diff --git a/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so b/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so index cda8920..0a3e077 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so and b/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so index cda8920..0a3e077 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so differ diff --git a/src/sxmo-dmenu/dmenu.c b/src/sxmo-dmenu/dmenu.c index d4bac4c..b1cd33b 100644 --- a/src/sxmo-dmenu/dmenu.c +++ b/src/sxmo-dmenu/dmenu.c @@ -78,11 +78,25 @@ static struct item *matches, *matchend; static struct item *prev, *curr, *next, *sel, *nav; static int mon = -1, screen; static int use_text_input = 0; /* selection/output mode: with -t, Shift inverts item vs typed text */ -static int engage_ime = 1; /* engage fcitx (XIM) for text entry by default so the T9 input method activates on dmenu prompts */ +static int engage_ime = 0; /* built-in zero-lag multi-tap T9 by default; -ime enables XIM */ static int initialindex = 0; static int managed = 0; static int curpos = 0, oldcurlen = 0; +enum { T9_LOWER = 0, T9_SHIFT = 1, T9_UPPER = 2, T9_SYM = 3, T9_NUM = 4 }; +#define T9_KEY_STAR 10 +#define T9_KEY_POUND 11 + +static int t9_mode = T9_LOWER; +static int t9_prev_mode = T9_LOWER; +static int t9_last_key = -1; +static int t9_tap_idx = 0; +static int t9_last_upper = 0; +static long long t9_last_ms = 0; +static int t9_held_key = -1; +static long long t9_held_since_ms = 0; +static int t9_longpress_done = 0; + static int daemon_mode = 0; static int menu_active = 0; static int menu_mode = MODE_NONE; @@ -232,13 +246,58 @@ add_menu_item(const char *label, const char *action, int loop) items[items_count].right = NULL; } +static const char * +t9_mode_badge(int mode) +{ + switch (mode) { + case T9_SHIFT: return "[Abc]"; + case T9_UPPER: return "[ABC]"; + case T9_SYM: return "[Sym]"; + case T9_NUM: return "[123]"; + default: return "[abc]"; + } +} + +static void +update_t9_status_indicator(void) +{ + const char *rdir = getenv("XDG_RUNTIME_DIR"); + const char *base = (rdir && rdir[0]) ? rdir : "/run/user/10000"; + const char *badge = t9_mode_badge(t9_mode); + char path[256]; + + snprintf(path, sizeof(path), "%s/sxmo_t9_mode", base); + FILE *fp = fopen(path, "w"); + if (fp) { + fprintf(fp, "%s\n", badge); + fclose(fp); + } + snprintf(path, sizeof(path), "%s/sxmo_status/default/005-input-method", base); + fp = fopen(path, "w"); + if (fp) { + fprintf(fp, "%s\n", badge); + fclose(fp); + } +} + +static void +reset_t9_state(void) +{ + t9_last_key = -1; + t9_tap_idx = 0; + t9_last_ms = 0; + t9_held_key = -1; + t9_held_since_ms = 0; + t9_longpress_done = 0; +} + static void reset_session_defaults(int case_insensitive, const char *new_prompt) { free_items(); topbar = 1; centered = 0; - use_text_input = 0; /* reset selection/output mode; fcitx routing (engage_ime) stays on */ + use_text_input = 0; initialindex = 0; fstrncmp = case_insensitive ? strncasecmp : strncmp; fstrstr = case_insensitive ? cistrstr : strstr; @@ -250,6 +309,11 @@ reset_session_defaults(int case_insensitive, const char *new_prompt) curpos = 0; oldcurlen = 0; inputw = 0; + reset_t9_state(); + if (prompt && (strstr(prompt, "PIN") || strstr(prompt, "PUK"))) + t9_mode = T9_NUM; + else + t9_mode = T9_LOWER; } static void @@ -379,10 +443,18 @@ recalculatenumbers(void) } for (item = items; item && item->text; item++) denom++; - if (numer == denom) - snprintf(numbers, NUMBERSBUFSIZE, "%d", numer); - else - snprintf(numbers, NUMBERSBUFSIZE, "%d/%d", numer, denom); + if (!engage_ime) { + const char *badge = t9_mode_badge(t9_mode); + if (numer == denom) + snprintf(numbers, NUMBERSBUFSIZE, "%s %d", badge, numer); + else + snprintf(numbers, NUMBERSBUFSIZE, "%s %d/%d", badge, numer, denom); + } else { + if (numer == denom) + snprintf(numbers, NUMBERSBUFSIZE, "%d", numer); + else + snprintf(numbers, NUMBERSBUFSIZE, "%d/%d", numer, denom); + } } static void @@ -1184,6 +1256,166 @@ spawn_xdrawer_and_close(void) trigger_plugin_open("xdrawer"); } +static int +map_t9_key(XKeyEvent *ev) +{ + if (ev->state & (ControlMask | Mod1Mask | Mod4Mask)) + return -1; + KeySym ks = XLookupKeysym(ev, 0); + unsigned int kc = ev->keycode; + if (kc == 530 || kc == 63 || ks == XK_asterisk || + ks == XK_KP_Multiply || (unsigned int)ks == 0x1008ffaa) + return T9_KEY_STAR; + if (kc == 531 || kc == 106 || ks == XK_numbersign || + ks == XK_KP_Divide || (unsigned int)ks == 0x1008ffab) + return T9_KEY_POUND; + if (kc >= 10 && kc <= 18) + return (int)(kc - 9); + if (kc == 19) + return 0; + if (ks >= XK_0 && ks <= XK_9) + return (int)(ks - XK_0); + if (ks >= XK_KP_0 && ks <= XK_KP_9) + return (int)(ks - XK_KP_0); + return -1; +} + +static const char * +get_t9_chars(int key_id, int mode, int upper) +{ + if (mode == T9_SYM) { + switch (key_id) { + case 0: return " 0"; + case 1: return ".,!?:;-_1"; + case 2: return "@#$%^&2"; + case 3: return "()[]{}<>3"; + case 4: return "+-*/=_4"; + case 5: return "|\\~`'\"5"; + case 6: return "'\"`~^6"; + case 7: return "/\\|_-+7"; + case 8: return "<>!=?&8"; + case 9: return "$%^&*#@9"; + case T9_KEY_STAR: return "*#+-=_/\\|~`@$%^&()[]{}<>'\".,!?:;"; + default: return ""; + } + } + switch (key_id) { + case 0: return " 0"; + case 1: return ".,-_!?/@:;'\"+=*#$%^&()[]{}<>|\\~`1"; + case 2: return upper ? "ABC2abc" : "abc2ABC"; + case 3: return upper ? "DEF3def" : "def3DEF"; + case 4: return upper ? "GHI4ghi" : "ghi4GHI"; + case 5: return upper ? "JKL5jkl" : "jkl5JKL"; + case 6: return upper ? "MNO6mno" : "mno6MNO"; + case 7: return upper ? "PQRS7pqrs" : "pqrs7PQRS"; + case 8: return upper ? "TUV8tuv" : "tuv8TUV"; + case 9: return upper ? "WXYZ9wxyz" : "wxyz9WXYZ"; + case T9_KEY_STAR: return "*#+-=_/\\|~`@$%^&()[]{}<>'\".,!?:;"; + default: return ""; + } +} + +static void +handle_t9_longpress(int key_id) +{ + if (key_id < 0 || t9_longpress_done) + return; + t9_longpress_done = 1; + t9_last_key = -1; + t9_tap_idx = 0; + + if (key_id >= 0 && key_id <= 9) { + if (t9_mode != T9_NUM && cursor > 0) { + text[cursor - 1] = (char)('0' + key_id); + match(); + if (menu_active || !daemon_mode) + drawmenu(); + } + } else if (key_id == T9_KEY_STAR) { + if (t9_mode == T9_NUM && cursor > 0) { + text[cursor - 1] = '.'; + match(); + if (menu_active || !daemon_mode) + drawmenu(); + } + } else if (key_id == T9_KEY_POUND) { + t9_mode = t9_prev_mode; + update_t9_status_indicator(); + insert("#", 1); + if (menu_active || !daemon_mode) + drawmenu(); + } +} + +static void +check_t9_longpress_timeout(void) +{ + if (engage_ime || t9_held_key < 0 || t9_longpress_done || t9_held_since_ms <= 0) + return; + if (dmenu_now_ms() - t9_held_since_ms >= 500) + handle_t9_longpress(t9_held_key); +} + +static void +handle_t9_keypress(int key_id) +{ + long long now = dmenu_now_ms(); + if (t9_held_key == key_id) { + if (!t9_longpress_done && (now - t9_held_since_ms) >= 500) + handle_t9_longpress(key_id); + return; + } + t9_held_key = key_id; + t9_held_since_ms = now; + t9_longpress_done = 0; + + if (key_id == T9_KEY_POUND) { + t9_last_key = -1; + t9_tap_idx = 0; + t9_prev_mode = t9_mode; + t9_mode = (t9_mode + 1) % 5; + update_t9_status_indicator(); + if (menu_active || !daemon_mode) + drawmenu(); + return; + } + + if (t9_mode == T9_NUM) { + char ch = (key_id == T9_KEY_STAR) ? '*' : (char)('0' + key_id); + t9_last_key = -1; + t9_tap_idx = 0; + insert(&ch, 1); + if (menu_active || !daemon_mode) + drawmenu(); + return; + } + + int same_key = (key_id == t9_last_key && (now - t9_last_ms) <= 850 && cursor > 0); + if (same_key) { + const char *chars = get_t9_chars(key_id, t9_mode, t9_last_upper); + size_t clen = strlen(chars); + if (clen > 0) { + t9_tap_idx = (t9_tap_idx + 1) % (int)clen; + text[cursor - 1] = chars[t9_tap_idx]; + match(); + } + } else { + t9_last_key = key_id; + t9_tap_idx = 0; + t9_last_upper = (t9_mode == T9_SHIFT || t9_mode == T9_UPPER); + const char *chars = get_t9_chars(key_id, t9_mode, t9_last_upper); + if (t9_mode == T9_SHIFT) { + t9_mode = T9_LOWER; + update_t9_status_indicator(); + } + if (chars[0]) + insert(&chars[0], 1); + } + t9_last_ms = now; + if (menu_active || !daemon_mode) + drawmenu(); +} + static void keypress(XKeyEvent *ev) { @@ -1192,6 +1424,15 @@ keypress(XKeyEvent *ev) KeySym ksym; Status status; + if (!engage_ime) { + int t9k = map_t9_key(ev); + if (t9k >= 0) { + handle_t9_keypress(t9k); + return; + } + reset_t9_state(); + } + ksym = NoSymbol; if (xic && ev->keycode == 0) len = Xutf8LookupString(xic, ev, buf, sizeof buf, &ksym, &status); @@ -2679,6 +2920,9 @@ show_menu(void) soft_pending = 0; soft_deadline = 0; + reset_t9_state(); + if (!engage_ime) + update_t9_status_indicator(); if (!menu_active) { menu_active = 1; @@ -2733,6 +2977,7 @@ hide_menu(void) mark_menu_active(0); soft_pending = 0; soft_deadline = 0; + reset_t9_state(); if (xic && engage_ime) { char *r = Xutf8ResetIC(xic); @@ -2855,6 +3100,10 @@ handle_client_socket(int sock_fd) fstrstr = cistrstr; } else if (!strcmp(c_argv[i], "-t")) use_text_input = 1; + else if (!strcmp(c_argv[i], "-ime")) + engage_ime = 1; + else if (!strcmp(c_argv[i], "-noime")) + engage_ime = 0; else if (!strcmp(c_argv[i], "-l") && i + 1 < actual_argc) lines = atoi(c_argv[++i]); else if (!strcmp(c_argv[i], "-H") && i + 1 < actual_argc) @@ -2865,6 +3114,8 @@ handle_client_socket(int sock_fd) free(session_prompt); session_prompt = strdup(c_argv[++i]); prompt = session_prompt; + if (prompt && (strstr(prompt, "PIN") || strstr(prompt, "PUK"))) + t9_mode = T9_NUM; } else if ((!strcmp(c_argv[i], "-fn") || !strcmp(c_argv[i], "-nb") || !strcmp(c_argv[i], "-nf") || !strcmp(c_argv[i], "-sb") || !strcmp(c_argv[i], "-sf") || !strcmp(c_argv[i], "-w")) && @@ -2995,6 +3246,28 @@ process_x11_events(void) if (!initial_released || dmenu_now_ms() < start_ignore) continue; } + if (!engage_ime && ev.type == KeyRelease) { + int t9k = map_t9_key(&ev.xkey); + if (t9k >= 0) { + if (XEventsQueued(dpy, QueuedAfterReading) > 0) { + XEvent nev; + XPeekEvent(dpy, &nev); + if (nev.type == KeyPress && nev.xkey.time == ev.xkey.time && + nev.xkey.keycode == ev.xkey.keycode) { + XNextEvent(dpy, &nev); + if (!t9_longpress_done && (dmenu_now_ms() - t9_held_since_ms) >= 450) + handle_t9_longpress(t9k); + continue; + } + } + if (t9_held_key == t9k) { + t9_held_key = -1; + t9_held_since_ms = 0; + t9_longpress_done = 0; + } + continue; + } + } if (engage_ime && !issoftkey(ev.xkey.keycode) && !(is_enter && items)) { ev.xkey.window = win; if (XFilterEvent(&ev, None)) @@ -3025,7 +3298,7 @@ process_x11_events(void) if (any_other_plugin_active()) { if (daemon_mode) cancel_session(); - } else { + } else if (ev.xvisibility.state == VisibilityFullyObscured) { XRaiseWindow(dpy, win); grabkeyboard(); drw_map(drw, win, 0, 0, mw, mh); @@ -3084,6 +3357,9 @@ run_standalone(void) int xfd = ConnectionNumber(dpy); soft_pending = 0; soft_deadline = 0; + reset_t9_state(); + if (!engage_ime) + update_t9_status_indicator(); start_ignore = dmenu_now_ms() + 40; initial_released = (query_softkeys_down() == 0); read_softkey_state(&init_soft_mask, &init_soft_ms); @@ -3091,14 +3367,20 @@ run_standalone(void) for (;;) { process_x11_events(); check_softkey_timeout(); + check_t9_longpress_timeout(); + int timeout_ms = initial_released ? -1 : 40; if (soft_pending && soft_deadline > 0) { - long long rem = MAX(1LL, soft_deadline - dmenu_now_ms()); - struct pollfd pfd = { xfd, POLLIN, 0 }; - poll(&pfd, 1, (int)rem); - } else { - struct pollfd pfd = { xfd, POLLIN, 0 }; - poll(&pfd, 1, initial_released ? -1 : 40); + int rem = (int)MAX(1LL, soft_deadline - dmenu_now_ms()); + if (timeout_ms < 0 || rem < timeout_ms) + timeout_ms = rem; } + if (!engage_ime && t9_held_key >= 0 && !t9_longpress_done && t9_held_since_ms > 0) { + int rem = (int)MAX(1LL, (t9_held_since_ms + 500) - dmenu_now_ms()); + if (timeout_ms < 0 || rem < timeout_ms) + timeout_ms = rem; + } + struct pollfd pfd = { xfd, POLLIN, 0 }; + poll(&pfd, 1, timeout_ms); } } @@ -3109,8 +3391,10 @@ run_daemon_loop(int fifo_fd, int sock_fd) for (;;) { process_x11_events(); - if (menu_active) + if (menu_active) { check_softkey_timeout(); + check_t9_longpress_timeout(); + } int timeout_ms = -1; if (menu_active) { @@ -3118,6 +3402,11 @@ run_daemon_loop(int fifo_fd, int sock_fd) timeout_ms = (int)MAX(1LL, soft_deadline - dmenu_now_ms()); else if (!initial_released) timeout_ms = 40; + if (!engage_ime && t9_held_key >= 0 && !t9_longpress_done && t9_held_since_ms > 0) { + int rem = (int)MAX(1LL, (t9_held_since_ms + 500) - dmenu_now_ms()); + if (timeout_ms < 0 || rem < timeout_ms) + timeout_ms = rem; + } } struct pollfd pfds[3]; @@ -3303,7 +3592,7 @@ setup(int map_immediately) grabkeyboard(); grabfocus(); } - if (xic) + if (xic && engage_ime) XSetICFocus(xic); } } @@ -3375,6 +3664,10 @@ dmenu_run_main(int argc, char *argv[]) fstrstr = cistrstr; } else if (!strcmp(argv[i], "-t")) use_text_input = 1; + else if (!strcmp(argv[i], "-ime")) + engage_ime = 1; + else if (!strcmp(argv[i], "-noime")) + engage_ime = 0; else if (!strcmp(argv[i], "-wm")) managed = 1; else if (i + 1 == argc)