dmenu: implement built-in zero-lag in-place multi-tap T9 input
This commit is contained in:
Binary file not shown.
Binary file not shown.
+308
-15
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user