1 /*
2 * Copyright (C) 2020 The HIME team, Taiwan
3 * Copyright (C) 2011 Edward Der-Hua Liu, Hsin-Chu, Taiwan
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation version 2.1
8 * of the License.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #include <sys/stat.h>
21
22 #include <X11/extensions/XTest.h>
23
24 #include "hime.h"
25
26 #include "gtab.h"
27 #include "win-kbm.h"
28
29 static GtkWidget *gwin_kbm = NULL;
30 static int kbm_timeout_handle;
31
32 #if !GTK_CHECK_VERSION(2, 91, 6)
33 static GdkColor red;
34 #else
35 static GdkRGBA red;
36 #endif
37
38 gboolean win_kbm_on = FALSE;
39
40 enum {
41 K_FILL = 1,
42 K_HOLD = 2,
43 K_PRESS = 4,
44 K_AREA_R = 8,
45 K_CAPSLOCK = 16
46 };
47
48 typedef struct {
49 KeySym keysym;
50 unich_t *enkey;
51 char shift_key;
52 char flag;
53 GtkWidget *lab, *but, *laben;
54 } KEY;
55
56 #if TRAY_ENABLED
57 extern void update_item_active_all ();
58 #endif
59
60 /**
61 @brief Virtual keyboard definition
62
63 Some rare users maybe need to translate those key defines.
64 So we kept those N_("stuff").
65
66 Note that our po/Makefile do not search .h files so those
67 strings will not present (by default) in .pot nor .po files.
68
69 */
70 #define ROWN 6
71 #define COLN 19
72 static KEY keys[ROWN][COLN] = {
73 {{XK_Escape, N_ ("Esc")},
74 {XK_F1, N_ ("F1")},
75 {XK_F2, N_ ("F2")},
76 {XK_F3, N_ ("F3")},
77 {XK_F4, N_ ("F4")},
78 {XK_F5, N_ ("F5")},
79 {XK_F6, N_ ("F6")},
80 {XK_F7, N_ ("F7")},
81 {XK_F8, N_ ("F8")},
82 {XK_F9, N_ ("F9")},
83 {XK_F10, N_ ("F10")},
84 {XK_F11, N_ ("F11")},
85 {XK_F12, N_ ("F12")},
86 {XK_Print, N_ ("Pr"), 0, K_AREA_R},
87 {XK_Scroll_Lock, N_ ("Slk"), 0, K_AREA_R},
88 {XK_Pause, N_ ("Pau"), 0, K_AREA_R}},
89
90 {{'`', N_ (" ` "), '~'},
91 {'1', N_ (" 1 "), '!'},
92 {'2', N_ (" 2 "), '@'},
93 {'3', N_ (" 3 "), '#'},
94 {'4', N_ (" 4 "), '$'},
95 {'5', N_ (" 5 "), '%'},
96 {'6', N_ (" 6 "), '^'},
97 {'7', N_ (" 7 "), '&'},
98 {'8', N_ (" 8 "), '*'},
99 {'9', N_ (" 9 "), '('},
100 {'0', N_ (" 0 "), ')'},
101 {'-', N_ (" - "), '_'},
102 {'=', N_ (" = "), '+'},
103 {XK_BackSpace, N_ ("←"), 0, K_FILL},
104 {XK_Insert, N_ ("Ins"), 0, K_AREA_R},
105 {XK_Home, N_ ("Ho"), 0, K_AREA_R},
106 {XK_Prior, N_ ("P↑"), 0, K_AREA_R}},
107
108 {{XK_Tab, N_ ("Tab")},
109 {'q', N_ (" q ")},
110 {'w', N_ (" w ")},
111 {'e', N_ (" e ")},
112 {'r', N_ (" r ")},
113 {'t', N_ (" t ")},
114 {'y', N_ (" y ")},
115 {'u', N_ (" u ")},
116 {'i', N_ (" i ")},
117 {'o', N_ (" o ")},
118 {'p', N_ (" p ")},
119 {'[', N_ (" [ "), '{'},
120 {']', N_ (" ] "), '}'},
121 {'\\', N_ (" \\ "), '|', K_FILL},
122 {XK_Delete, N_ ("Del"), 0, K_AREA_R},
123 {XK_End, N_ ("En"), 0, K_AREA_R},
124 {XK_Next, N_ ("P↓"), 0, K_AREA_R}},
125
126 {{XK_Caps_Lock, N_ ("Caps"), 0, K_CAPSLOCK},
127 {'a', N_ (" a ")},
128 {'s', N_ (" s ")},
129 {'d', N_ (" d ")},
130 {'f', N_ (" f ")},
131 {'g', N_ (" g ")},
132 {'h', N_ (" h ")},
133 {'j', N_ (" j ")},
134 {'k', N_ (" k ")},
135 {'l', N_ (" l ")},
136 {';', N_ (" ; "), ':'},
137 {'\'', N_ (" ' "), '"'},
138 {XK_Return, N_ (" Enter "), 0, K_FILL},
139 {XK_Num_Lock, N_ ("Num"), 0, K_AREA_R},
140 {XK_KP_Add, N_ (" + "), 0, K_AREA_R}},
141
142 {{XK_Shift_L, N_ (" Shift "), 0, K_HOLD},
143 {'z', N_ (" z ")},
144 {'x', N_ (" x ")},
145 {'c', N_ (" c ")},
146 {'v', N_ (" v ")},
147 {'b', N_ (" b ")},
148 {'n', N_ (" n ")},
149 {'m', N_ (" m ")},
150 {',', N_ (" , "), '<'},
151 {'.', N_ (" . "), '>'},
152 {'/', N_ (" / "), '?'},
153 {XK_Shift_R, N_ (" Shift"), 0, K_HOLD | K_FILL},
154 {XK_KP_Multiply, N_ (" * "), 0, K_AREA_R},
155 {XK_Up, N_ ("↑"), 0, K_AREA_R}},
156
157 {{XK_Control_L, N_ ("Ctrl"), 0, K_HOLD},
158 {XK_Super_L, N_ ("◆")},
159 {XK_Alt_L, N_ ("Alt"), 0, K_HOLD},
160 {' ', N_ ("Space"), 0, K_FILL},
161 {XK_Alt_R, N_ ("Alt"), 0, K_HOLD},
162 {XK_Super_R, N_ ("◆")},
163 {XK_Menu, N_ ("■")},
164 {XK_Control_R, N_ ("Ctrl"), 0, K_HOLD},
165 {XK_Left, N_ ("←"), 0, K_AREA_R},
166 {XK_Down, N_ ("↓"), 0, K_AREA_R},
167 {XK_Right, N_ ("→"), 0, K_AREA_R}}};
168
169 static int keysN = sizeof (keys) / sizeof (keys[0]);
170
171 #if !GTK_CHECK_VERSION(3, 0, 0)
mod_fg_all(GtkWidget * label,GdkColor * col)172 static void mod_fg_all (GtkWidget *label, GdkColor *col) {
173 if (!label) {
174 return;
175 }
176
177 gtk_widget_modify_fg (label, GTK_STATE_NORMAL, col);
178 gtk_widget_modify_fg (label, GTK_STATE_ACTIVE, col);
179 gtk_widget_modify_fg (label, GTK_STATE_SELECTED, col);
180 gtk_widget_modify_fg (label, GTK_STATE_PRELIGHT, col);
181 }
182 #else
mod_fg_all(GtkWidget * label,GdkRGBA * rgbfg)183 static void mod_fg_all (GtkWidget *label, GdkRGBA *rgbfg) {
184 if (!label) {
185 return;
186 }
187
188 gtk_widget_override_color (label, GTK_STATE_FLAG_NORMAL, rgbfg);
189 gtk_widget_override_color (label, GTK_STATE_FLAG_ACTIVE, rgbfg);
190 gtk_widget_override_color (label, GTK_STATE_FLAG_SELECTED, rgbfg);
191 gtk_widget_override_color (label, GTK_STATE_FLAG_PRELIGHT, rgbfg);
192 }
193 #endif
194
send_fake_key_eve2(const KeySym key,const gboolean press)195 static void send_fake_key_eve2 (const KeySym key, const gboolean press) {
196 const KeyCode kc = XKeysymToKeycode (dpy, key);
197 XTestFakeKeyEvent (dpy, kc, press, CurrentTime);
198 }
199
timeout_repeat(gpointer data)200 static gboolean timeout_repeat (gpointer data) {
201 const KeySym k = GPOINTER_TO_INT (data);
202 send_fake_key_eve2 (k, TRUE);
203 return TRUE;
204 }
205
timeout_first_time(gpointer data)206 static gboolean timeout_first_time (gpointer data) {
207 const KeySym k = GPOINTER_TO_INT (data);
208 dbg ("timeout_first_time %c\n", k);
209 send_fake_key_eve2 (k, TRUE);
210 kbm_timeout_handle = g_timeout_add (50, timeout_repeat, data);
211 return FALSE;
212 }
213
clear_hold(KEY * k)214 static void clear_hold (KEY *k) {
215 KeySym keysym = k->keysym;
216 GtkWidget *laben = k->laben;
217 k->flag &= ~K_PRESS;
218 mod_fg_all (laben, NULL);
219 send_fake_key_eve2 (keysym, FALSE);
220 }
221
timeout_clear_hold(gpointer data)222 static gboolean timeout_clear_hold (gpointer data) {
223 clear_hold ((KEY *) data);
224 return FALSE;
225 }
226
clear_kbm_timeout_handle(void)227 static void clear_kbm_timeout_handle (void) {
228 if (!kbm_timeout_handle) {
229 return;
230 }
231 g_source_remove (kbm_timeout_handle);
232 kbm_timeout_handle = 0;
233 }
234
cb_button_click(GtkWidget * wid,KEY * k)235 static void cb_button_click (GtkWidget *wid, KEY *k) {
236 KeySym keysym = k->keysym;
237 GtkWidget *laben = k->laben;
238
239 dbg ("cb_button_click keysym %d\n", keysym);
240
241 if (k->flag & K_HOLD) {
242 if (k->flag & K_PRESS) {
243 clear_hold (k);
244 } else {
245 send_fake_key_eve2 (keysym, TRUE);
246 k->flag |= K_PRESS;
247 mod_fg_all (laben, &red);
248 g_timeout_add (10000, timeout_clear_hold, GINT_TO_POINTER (k));
249 }
250 } else {
251 clear_kbm_timeout_handle ();
252 kbm_timeout_handle = g_timeout_add (500, timeout_first_time, GINT_TO_POINTER (keysym));
253 send_fake_key_eve2 (keysym, TRUE);
254 }
255 }
256
cb_button_release(GtkWidget * wid,KEY * k)257 static void cb_button_release (GtkWidget *wid, KEY *k) {
258 dbg ("cb_button_release %d\n", kbm_timeout_handle);
259 clear_kbm_timeout_handle ();
260
261 send_fake_key_eve2 (k->keysym, FALSE);
262
263 for (int i = 0; i < keysN; i++) {
264 for (int j = 0; keys[i][j].enkey; j++) {
265 if (!(keys[i][j].flag & K_PRESS)) {
266 continue;
267 }
268 keys[i][j].flag &= ~K_PRESS;
269 send_fake_key_eve2 (keys[i][j].keysym, FALSE);
270 mod_fg_all (keys[i][j].laben, NULL);
271 }
272 }
273 }
274
create_win_kbm(void)275 static void create_win_kbm (void) {
276 #if !GTK_CHECK_VERSION(3, 0, 0)
277 gdk_color_parse ("red", &red);
278 #else
279 gdk_rgba_parse (&red, "red");
280 #endif
281
282 gwin_kbm = gtk_window_new (GTK_WINDOW_TOPLEVEL);
283 gtk_window_set_has_resize_grip (GTK_WINDOW (gwin_kbm), FALSE);
284 gtk_container_set_border_width (GTK_CONTAINER (gwin_kbm), 0);
285
286 GtkWidget *hbox_top = gtk_hbox_new (FALSE, 0);
287 gtk_container_add (GTK_CONTAINER (gwin_kbm), hbox_top);
288
289 GtkWidget *vbox_l = gtk_vbox_new (FALSE, 0);
290 gtk_orientable_set_orientation (GTK_ORIENTABLE (vbox_l), GTK_ORIENTATION_VERTICAL);
291 gtk_box_pack_start (GTK_BOX (hbox_top), vbox_l, FALSE, FALSE, 0);
292 gtk_container_set_border_width (GTK_CONTAINER (vbox_l), 0);
293
294 GtkWidget *vbox_r = gtk_vbox_new (FALSE, 0);
295 gtk_orientable_set_orientation (GTK_ORIENTABLE (vbox_r), GTK_ORIENTATION_VERTICAL);
296 gtk_box_pack_start (GTK_BOX (hbox_top), vbox_r, FALSE, FALSE, 0);
297 gtk_container_set_border_width (GTK_CONTAINER (vbox_r), 0);
298
299 for (int i = 0; i < keysN; i++) {
300 GtkWidget *hboxl = gtk_hbox_new (FALSE, 0);
301 gtk_container_set_border_width (GTK_CONTAINER (hboxl), 0);
302 gtk_box_pack_start (GTK_BOX (vbox_l), hboxl, FALSE, FALSE, 0);
303
304 GtkWidget *hboxr = gtk_hbox_new (FALSE, 0);
305 gtk_container_set_border_width (GTK_CONTAINER (hboxr), 0);
306 gtk_box_pack_start (GTK_BOX (vbox_r), hboxr, FALSE, FALSE, 0);
307
308 KEY *pk = keys[i];
309 for (int j = 0; pk[j].enkey; j++) {
310 KEY *ppk = &pk[j];
311 const char flag = ppk->flag;
312 if (!ppk->keysym) {
313 continue;
314 }
315 GtkWidget *but = pk[j].but = gtk_button_new ();
316 gtk_container_set_border_width (GTK_CONTAINER (but), 0);
317
318 g_signal_connect (G_OBJECT (but), "pressed", G_CALLBACK (cb_button_click), ppk);
319 if (!(ppk->flag & K_HOLD)) {
320 g_signal_connect (G_OBJECT (but), "released", G_CALLBACK (cb_button_release), ppk);
321 }
322
323 GtkWidget *hbox = (flag & K_AREA_R) ? hboxr : hboxl;
324
325 if (flag & K_FILL) {
326 gtk_box_pack_start (GTK_BOX (hbox), but, TRUE, TRUE, 0);
327 } else {
328 gtk_box_pack_start (GTK_BOX (hbox), but, FALSE, FALSE, 0);
329 }
330
331 GtkWidget *v = gtk_vbox_new (FALSE, 0);
332 gtk_orientable_set_orientation (GTK_ORIENTABLE (v), GTK_ORIENTATION_VERTICAL);
333 gtk_container_set_border_width (GTK_CONTAINER (v), 0);
334 gtk_container_add (GTK_CONTAINER (but), v);
335
336 GtkWidget *laben = ppk->laben = gtk_label_new (_ (ppk->enkey));
337 set_label_font_size (laben, hime_font_size_win_kbm_en);
338 gtk_box_pack_start (GTK_BOX (v), laben, FALSE, FALSE, 0);
339
340 if (0 < i && i < 5) {
341 GtkWidget *label = ppk->lab = gtk_label_new (" ");
342 gtk_box_pack_start (GTK_BOX (v), label, FALSE, FALSE, 0);
343 }
344 }
345 }
346
347 gtk_widget_realize (gwin_kbm);
348 set_no_focus (gwin_kbm);
349 }
350
351 #if TRAY_ENABLED
352 extern GtkStatusIcon *tray_icon;
353 extern GtkStatusIcon *icon_main;
354
355 extern gboolean is_exist_tray ();
356 extern gboolean is_exist_tray_double ();
357 #endif
358
move_win_kbm(void)359 static void move_win_kbm (void) {
360 int width = 0;
361 int height = 0;
362 get_win_size (gwin_kbm, &width, &height);
363
364 int ox = 0;
365 int oy = 0;
366
367 #if TRAY_ENABLED
368 GdkRectangle r;
369 GtkOrientation ori;
370
371 if (
372 (is_exist_tray () && gtk_status_icon_get_geometry (tray_icon, NULL, &r, &ori)) ||
373 (is_exist_tray_double () && gtk_status_icon_get_geometry (icon_main, NULL, &r, &ori))) {
374 ox = r.x;
375 if (ox + width > dpy_xl) {
376 ox = dpy_xl - width;
377 }
378
379 if (r.y < 100) {
380 oy = r.y + r.height;
381 } else {
382 oy = r.y - height;
383 }
384 } else
385 #endif
386 {
387 ox = dpy_xl - width;
388 oy = dpy_yl - height - 16;
389 }
390
391 gtk_window_move (GTK_WINDOW (gwin_kbm), ox, oy);
392 }
393
show_win_kbm(void)394 void show_win_kbm (void) {
395 if (!gwin_kbm) {
396 create_win_kbm ();
397 update_win_kbm ();
398 }
399
400 gtk_widget_show_all (gwin_kbm);
401 win_kbm_on = TRUE;
402
403 #if TRAY_ENABLED
404 update_item_active_all ();
405 #endif
406
407 move_win_kbm ();
408 }
409
410 #include "pho.h"
411
get_keys_ent(KeySym keysym)412 static KEY *get_keys_ent (KeySym keysym) {
413 const char shift_chars[] = "~!@#$%^&*()_+{}|:\"<>?";
414 const char shift_chars_o[] = "`1234567890-=[]\\;',./";
415
416 for (int i = 0; i < keysN; i++) {
417 for (int j = 0; j < COLN; j++) {
418 char *p = NULL;
419 if (keysym >= 'A' && keysym <= 'Z') {
420 keysym += 0x20;
421 } else if ((p = strchr (shift_chars, keysym))) {
422 keysym = shift_chars_o[p - shift_chars];
423 }
424
425 if (keys[i][j].keysym != keysym) {
426 continue;
427 }
428 return &keys[i][j];
429 }
430 }
431
432 return NULL;
433 }
434
set_kbm_key(const KeySym keysym,char * str)435 static void set_kbm_key (const KeySym keysym, char *str) {
436 if (!gwin_kbm) {
437 return;
438 }
439
440 const KEY *p = get_keys_ent (keysym);
441 if (!p) {
442 return;
443 }
444
445 GtkWidget *label = p->lab;
446 char *t = (char *) gtk_label_get_text (GTK_LABEL (label));
447 char tt[64];
448
449 if (t && strcmp (t, str)) {
450 strcat (strcpy (tt, t), str);
451 str = tt;
452 }
453
454 if (label) {
455 gtk_label_set_text (GTK_LABEL (label), str);
456 set_label_font_size (label, hime_font_size_win_kbm);
457 }
458 }
459
clear_kbm(void)460 static void clear_kbm (void) {
461 for (int i = 0; i < keysN; i++) {
462 for (int j = 0; j < COLN; j++) {
463 GtkWidget *label = keys[i][j].lab;
464 if (label) {
465 gtk_label_set_text (GTK_LABEL (label), NULL);
466 }
467
468 if (keys[i][j].laben) {
469 gtk_label_set_text (GTK_LABEL (keys[i][j].laben), _ (keys[i][j].enkey));
470 }
471 }
472 }
473 }
474
display_shift_keys(void)475 static void display_shift_keys (void) {
476 for (int i = 127; i > 0; i--) {
477 const KEY *p = get_keys_ent (i);
478 if (p && p->shift_key) {
479 char *t = (char *) gtk_label_get_text (GTK_LABEL (p->lab));
480 if (t && t[0]) {
481 continue;
482 }
483 char tt[64];
484 tt[0] = p->shift_key;
485 tt[1] = 0;
486 set_kbm_key (i, tt);
487 }
488 }
489 }
490
update_win_kbm(void)491 void update_win_kbm (void) {
492 if (!current_CS || !gwin_kbm) {
493 return;
494 }
495
496 clear_kbm ();
497
498 if (current_CS->im_state != HIME_STATE_CHINESE) {
499 if (current_CS->im_state == HIME_STATE_DISABLED) {
500 for (int i = 0; i < keysN; i++) {
501 for (int j = 0; j < COLN; j++) {
502 char kstr[2];
503 kstr[0] = keys[i][j].shift_key;
504 kstr[1] = 0;
505
506 if (keys[i][j].laben) {
507 if (kstr[0]) {
508 gtk_label_set_text (GTK_LABEL (keys[i][j].laben), kstr);
509 }
510 set_label_font_size (keys[i][j].laben, hime_font_size_win_kbm_en);
511 }
512
513 if (keys[i][j].lab) {
514 if (kstr[0]) {
515 gtk_label_set_text (GTK_LABEL (keys[i][j].lab), _ (keys[i][j].enkey));
516 }
517 set_label_font_size (keys[i][j].lab, hime_font_size_win_kbm_en);
518 }
519 }
520 }
521 }
522 goto ret;
523 }
524
525 switch (current_method_type ()) {
526 case method_type_PHO:
527 case method_type_TSIN:
528 for (int i = 0; i < 128; i++) {
529 char tt[64];
530 int ttN = 0;
531
532 for (int j = 0; j < 3; j++) {
533 const int num = phkbm.phokbm[i][j].num;
534 const int typ = phkbm.phokbm[i][j].typ;
535 if (!num) {
536 continue;
537 }
538 ttN += utf8cpy (&tt[ttN], &pho_chars[typ][num * 3]);
539 }
540
541 if (!ttN) {
542 continue;
543 }
544 set_kbm_key (i, tt);
545 }
546
547 display_shift_keys ();
548 break;
549
550 case method_type_MODULE:
551 break;
552
553 default:
554 if (!cur_inmd || !cur_inmd->DefChars) {
555 return;
556 }
557
558 for (int loop = 0; loop < 2; loop++) {
559 for (int i = 127; i > 0; i--) {
560 const char k = cur_inmd->keymap[i];
561 if (!k) {
562 continue;
563 }
564
565 char *keyname = &cur_inmd->keyname[k * CH_SZ];
566 if (!keyname[0]) {
567 continue;
568 }
569
570 if (loop == 0 && !(keyname[0] & 0x80)) {
571 continue;
572 }
573
574 if (loop == 1) {
575 const KEY *p = get_keys_ent (i);
576 char *t = (char *) gtk_label_get_text (GTK_LABEL (p->lab));
577 if (t && t[0]) {
578 continue;
579 }
580 }
581
582 char tt[64];
583 tt[0] = 0;
584 if (keyname[0] & 128) {
585 utf8cpy (tt, keyname);
586 } else {
587 tt[1] = 0;
588 memcpy (tt, keyname, 2);
589 tt[2] = 0;
590 }
591
592 set_kbm_key (i, tt);
593 }
594 }
595
596 display_shift_keys ();
597
598 break;
599 }
600
601 ret:
602 move_win_kbm ();
603 }
604
hide_win_kbm(void)605 void hide_win_kbm (void) {
606 if (!gwin_kbm) {
607 return;
608 }
609
610 clear_kbm_timeout_handle ();
611
612 win_kbm_on = FALSE;
613
614 #if TRAY_ENABLED
615 update_item_active_all ();
616 #endif
617
618 gtk_widget_hide (gwin_kbm);
619 }
620
621 extern gboolean old_capslock_on;
622
win_kbm_disp_caplock()623 void win_kbm_disp_caplock () {
624 const KEY *p = get_keys_ent (XK_Caps_Lock);
625
626 if (old_capslock_on) {
627 mod_fg_all (p->laben, &red);
628 } else {
629 mod_fg_all (p->laben, NULL);
630 }
631 }
632