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