diff --git a/overlay/rootfs/usr/lib/dwm/plugins/contacts.so b/overlay/rootfs/usr/lib/dwm/plugins/contacts.so index 5acd39a..d72a7eb 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/contacts.so and b/overlay/rootfs/usr/lib/dwm/plugins/contacts.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/files.so b/overlay/rootfs/usr/lib/dwm/plugins/files.so index cea126b..f70b49f 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/files.so and b/overlay/rootfs/usr/lib/dwm/plugins/files.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/modem.so b/overlay/rootfs/usr/lib/dwm/plugins/modem.so index 0936afe..9dcc9e3 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/modem.so and b/overlay/rootfs/usr/lib/dwm/plugins/modem.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/networks.so b/overlay/rootfs/usr/lib/dwm/plugins/networks.so index 5c677cd..444e752 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/networks.so and b/overlay/rootfs/usr/lib/dwm/plugins/networks.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/sms.so b/overlay/rootfs/usr/lib/dwm/plugins/sms.so index 853edae..8d21caa 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/sms.so and b/overlay/rootfs/usr/lib/dwm/plugins/sms.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/themes.so b/overlay/rootfs/usr/lib/dwm/plugins/themes.so index 5783928..b2acfe9 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/themes.so and b/overlay/rootfs/usr/lib/dwm/plugins/themes.so differ diff --git a/overlay/rootfs/usr/lib/fcitx5/libt9.so b/overlay/rootfs/usr/lib/fcitx5/libt9.so index 57973d6..6398d78 100755 Binary files a/overlay/rootfs/usr/lib/fcitx5/libt9.so and b/overlay/rootfs/usr/lib/fcitx5/libt9.so differ diff --git a/overlay/rootfs/usr/lib/fcitx5/t9.so b/overlay/rootfs/usr/lib/fcitx5/t9.so index 57973d6..6398d78 100755 Binary files a/overlay/rootfs/usr/lib/fcitx5/t9.so and b/overlay/rootfs/usr/lib/fcitx5/t9.so differ diff --git a/prebuilt/fcitx5-t9/usr/lib/fcitx5/libt9.so b/prebuilt/fcitx5-t9/usr/lib/fcitx5/libt9.so index 57973d6..6398d78 100755 Binary files a/prebuilt/fcitx5-t9/usr/lib/fcitx5/libt9.so and b/prebuilt/fcitx5-t9/usr/lib/fcitx5/libt9.so differ diff --git a/prebuilt/fcitx5-t9/usr/lib/fcitx5/t9.so b/prebuilt/fcitx5-t9/usr/lib/fcitx5/t9.so index 57973d6..6398d78 100755 Binary files a/prebuilt/fcitx5-t9/usr/lib/fcitx5/t9.so and b/prebuilt/fcitx5-t9/usr/lib/fcitx5/t9.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so index 5acd39a..d72a7eb 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/contacts.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/files.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/files.so index cea126b..f70b49f 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/files.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/files.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/modem.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/modem.so index 0936afe..9dcc9e3 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/modem.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/modem.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/networks.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/networks.so index 5c677cd..444e752 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/networks.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/networks.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/sms.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/sms.so index 853edae..8d21caa 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/sms.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/sms.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/themes.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/themes.so index 5783928..b2acfe9 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/themes.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/themes.so differ diff --git a/src/dwm-plugins/contacts_plugin.cpp b/src/dwm-plugins/contacts_plugin.cpp index eb081e0..59eb78c 100644 --- a/src/dwm-plugins/contacts_plugin.cpp +++ b/src/dwm-plugins/contacts_plugin.cpp @@ -42,16 +42,16 @@ enum class TextCaseMode { }; const char* kMultiTapLower[10] = { - " 0+", // 0 - ".,-@_1", // 1 - "abc2", // 2 - "def3", // 3 - "ghi4", // 4 - "jkl5", // 5 - "mno6", // 6 - "pqrs7", // 7 - "tuv8", // 8 - "wxyz9" // 9 + " 0+", // 0 + kMultiTapPunct1, // 1 + "abc2", // 2 + "def3", // 3 + "ghi4", // 4 + "jkl5", // 5 + "mno6", // 6 + "pqrs7", // 7 + "tuv8", // 8 + "wxyz9" // 9 }; int runContactsApp(int argc, char** argv) { @@ -527,14 +527,15 @@ int runContactsApp(int argc, char** argv) { modeBadge); // Field 0: Name box (y = 44..108) - auto drawFieldBox = [&](int y, int h, bool active, const char* label, + auto drawFieldBox = [&](int y, int h, bool active, + const std::string& label, const std::string& val, const char* placeholder) { XSetForeground(dpy, gc, active ? theme.spec.bgInputFocus : theme.spec.bgButton); XFillRectangle(dpy, pix, gc, 6, y, kWinWidth - 12, h); XSetForeground(dpy, gc, active ? theme.spec.accent : theme.spec.border); XDrawRectangle(dpy, pix, gc, 6, y, kWinWidth - 13, h - 1); drawPango(layoutSmall, active ? &colYellow : &colMuted, 12, y + 3, 18, - label); + label, 214); std::string shown = val; if (active) { shown.push_back('|'); @@ -542,14 +543,19 @@ int runContactsApp(int argc, char** argv) { if (val.empty() && !active) { drawPango(layoutMain, &colMuted, 12, y + 23, 28, placeholder, 214); } else { - if (shown.size() > 21) { - shown = ".." + shown.substr(shown.size() - 19); - } + shown = tailFitPangoText(layoutMain, shown, 214); drawPango(layoutMain, &colWhite, 12, y + 23, 28, shown, 214); } }; - drawFieldBox(44, 56, editField == 0, "Name (# Input mode):", editName, + std::string nameHdr = "Name (# Input mode):"; + if (editField == 0 && lastTapDigit >= 0 && lastTapDigit <= 9 && + (nowMs() - lastTapMs) <= 850) { + nameHdr = formatMultiTapBar( + std::to_string(lastTapDigit) + ":", kMultiTapLower[lastTapDigit], + lastTapCycle, caseMode == TextCaseMode::Upper); + } + drawFieldBox(44, 56, editField == 0, nameHdr, editName, "Type name on keypad..."); drawFieldBox(106, 56, editField == 1, "Number (Hold 0 for +):", editNumber, "Enter phone number..."); @@ -863,7 +869,7 @@ int runContactsApp(int argc, char** argv) { } const char* seq = kMultiTapLower[digit]; int seqLen = static_cast(std::strlen(seq)); - if (digit == lastTapDigit && (now - lastTapMs) <= 650 && !editName.empty()) { + if (digit == lastTapDigit && (now - lastTapMs) <= 850 && !editName.empty()) { lastTapCycle = (lastTapCycle + 1) % seqLen; char ch = seq[lastTapCycle]; bool makeUpper = false; @@ -1751,6 +1757,12 @@ int runContactsApp(int argc, char** argv) { } } + if (visible && mode == ViewMode::Edit && lastTapDigit != -1 && + (nowMs() - lastTapMs) > 850) { + lastTapDigit = -1; + render(); + } + int timeoutMs = -1; if (visible) { timeoutMs = 200; @@ -1762,6 +1774,10 @@ int runContactsApp(int argc, char** argv) { int64_t rem = 420 - (nowMs() - zeroPressMs); timeoutMs = std::min(timeoutMs, static_cast(std::max(5, rem))); } + if (mode == ViewMode::Edit && lastTapDigit != -1) { + int64_t rem = 851 - (nowMs() - lastTapMs); + timeoutMs = std::min(timeoutMs, static_cast(std::max(5, rem))); + } } struct pollfd pfds[2] {}; diff --git a/src/dwm-plugins/files_plugin.cpp b/src/dwm-plugins/files_plugin.cpp index 5410cda..6b3e42b 100644 --- a/src/dwm-plugins/files_plugin.cpp +++ b/src/dwm-plugins/files_plugin.cpp @@ -53,44 +53,44 @@ enum class TextCaseMode { }; const char* kMultiTapFile[10] = { - " _-0", // 0 - ".-_1", // 1 - "abc2", // 2 - "def3", // 3 - "ghi4", // 4 - "jkl5", // 5 - "mno6", // 6 - "pqrs7", // 7 - "tuv8", // 8 - "wxyz9" // 9 + " _-0", // 0 + ".-_1,!?+@#$%~()[]{}", // 1 + "abc2", // 2 + "def3", // 3 + "ghi4", // 4 + "jkl5", // 5 + "mno6", // 6 + "pqrs7", // 7 + "tuv8", // 8 + "wxyz9" // 9 }; const char* kMultiTapEditorAlpha[11] = { - " 0", // 0 - ".,:;!?-_1/\"'()", // 1 - "abc2", // 2 - "def3", // 3 - "ghi4", // 4 - "jkl5", // 5 - "mno6", // 6 - "pqrs7", // 7 - "tuv8", // 8 - "wxyz9", // 9 - ".,!?+-*/=<>()[]{}\"'_#$%&|\\~^@" // 10 (*) + " 0", // 0 + kMultiTapPunct1, // 1 + "abc2", // 2 + "def3", // 3 + "ghi4", // 4 + "jkl5", // 5 + "mno6", // 6 + "pqrs7", // 7 + "tuv8", // 8 + "wxyz9", // 9 + kMultiTapStar // 10 (*) }; const char* kMultiTapEditorSym[11] = { - " _0", // 0 - ".,:;!?1", // 1 - "\"'`~@2", // 2 - "()[]{}<>3", // 3 - "+-*/=%4", // 4 - "|&^#$5", // 5 - "_\\/$@6", // 6 - "=<>!~7", // 7 - "();,\"8", // 8 - "{}[]#9", // 9 - "+-*/=<>()[]{}\"'.,:;!?_|&^#$%~@\\" // 10 (*) + " _0", // 0 + ".,:;!?-_1", // 1 + "\"'`~@#2", // 2 + "()[]{}<>3", // 3 + "+-*/=%4", // 4 + "|&^#$5", // 5 + "_\\/$@6", // 6 + "=<>!~7", // 7 + "();,\"8", // 8 + "{}[]#9", // 9 + kMultiTapStar // 10 (*) }; struct FileItem { @@ -1184,11 +1184,20 @@ int runFilesApp(int argc, char** argv) { XFillRectangle(dpy, pix, gc, 10, 74, kWinWidth - 20, 38); XSetForeground(dpy, gc, pt.spec.accentAlt); XDrawRectangle(dpy, pix, gc, 10, 74, kWinWidth - 21, 37); - drawPango(layoutMain, &colWhite, 16, 74, 38, inputValue + "|", - kWinWidth - 32); + std::string modalDisp = + tailFitPangoText(layoutMain, inputValue + "|", kWinWidth - 32); + drawPango(layoutMain, &colWhite, 16, 74, 38, modalDisp, kWinWidth - 32); - drawPango(layoutSmall, &colMuted, 10, 126, 20, - "# Input mode", 220); + if (lastTapDigit >= 0 && lastTapDigit <= 9 && + (nowMs() - lastTapMs) <= 850) { + std::string cand = formatMultiTapBar( + std::to_string(lastTapDigit) + ":", kMultiTapFile[lastTapDigit], + lastTapCycle, caseMode == TextCaseMode::Upper); + drawPango(layoutSmall, &colYellow, 10, 126, 20, cand, 220); + } else { + drawPango(layoutSmall, &colMuted, 10, 126, 20, + "#:Mode *:Punct Right:Next", 220); + } drawSoftkeyBar("Save", "Save", inputValue.empty() ? "Cancel" : "Del"); @@ -1279,8 +1288,28 @@ int runFilesApp(int argc, char** argv) { const std::string& rawLine = editLines[idx]; size_t sliceStart = 0; - if (isCurRow && editCursorCol > 22) { - sliceStart = clampUtf8Pos(rawLine, editCursorCol - 22); + int cx = 0; + const int maxLineTextW = kWinWidth - gutterW - 10; + if (isCurRow) { + size_t curByte = + static_cast(clampUtf8Pos(rawLine, editCursorCol)); + if (curByte > 22) { + sliceStart = + static_cast(clampUtf8Pos(rawLine, editCursorCol - 22)); + } + while (sliceStart < curByte) { + std::string beforeCur = expandTabsForDisplay( + rawLine.substr(sliceStart, curByte - sliceStart)); + pango_layout_set_width(layoutSmall, -1); + pango_layout_set_ellipsize(layoutSmall, PANGO_ELLIPSIZE_NONE); + pango_layout_set_text(layoutSmall, beforeCur.c_str(), + static_cast(beforeCur.size())); + int chh = 0; + pango_layout_get_pixel_size(layoutSmall, &cx, &chh); + if (cx <= maxLineTextW - 6) break; + sliceStart = static_cast( + nextUtf8Pos(rawLine, static_cast(sliceStart))); + } } std::string rawSlice = rawLine.substr(sliceStart); std::string dispSlice = expandTabsForDisplay(rawSlice); @@ -1289,16 +1318,6 @@ int runFilesApp(int argc, char** argv) { ry, rowH, dispSlice, kWinWidth - gutterW - 8); if (isCurRow && !editMenuOpen) { - size_t curOff = - (editCursorCol >= sliceStart) ? (editCursorCol - sliceStart) : 0; - std::string beforeCur = - expandTabsForDisplay(rawSlice.substr(0, curOff)); - pango_layout_set_width(layoutSmall, -1); - pango_layout_set_ellipsize(layoutSmall, PANGO_ELLIPSIZE_NONE); - pango_layout_set_text(layoutSmall, beforeCur.c_str(), - static_cast(beforeCur.size())); - int cx = 0, chh = 0; - pango_layout_get_pixel_size(layoutSmall, &cx, &chh); int curX = std::min(kWinWidth - 4, gutterW + 4 + cx); XSetForeground(dpy, gc, tapActive ? pt.spec.success : pt.spec.accentAlt); @@ -1318,24 +1337,13 @@ int runFilesApp(int argc, char** argv) { (editCaseMode == TextCaseMode::Sym) ? kMultiTapEditorSym[editLastTapDigit] : kMultiTapEditorAlpha[editLastTapDigit]; - int tlen = static_cast(std::strlen(table)); - std::string cand = + std::string pref = (editLastTapDigit == 10) - ? "*: " - : (std::to_string(editLastTapDigit) + ": "); - for (int k = 0; k < tlen; ++k) { - char c = table[k]; - if (std::isalpha(static_cast(c)) && - editCaseMode == TextCaseMode::Upper) { - c = static_cast(std::toupper(static_cast(c))); - } - std::string label = (c == ' ') ? "SPC" : std::string(1, c); - if (k == editLastTapCycle) { - cand += "[" + label + "] "; - } else { - cand += label + " "; - } - } + ? "*:" + : (std::to_string(editLastTapDigit) + ":"); + std::string cand = formatMultiTapBar( + pref, table, editLastTapCycle, + editCaseMode == TextCaseMode::Upper); drawPango(layoutSmall, &colYellow, 6, hintY, 20, cand, kWinWidth - 12); } else { drawPango(layoutSmall, &colMuted, 6, hintY, 20, @@ -2116,6 +2124,7 @@ int runFilesApp(int argc, char** argv) { handleSoftkeyAction(true); } else if (sym == XK_Right || sym == XK_KP_Right) { lastTapDigit = -1; + drawUI(); } else { char d = mapDialKey(sym, kc, ev.xkey.state); if (d == '#') { @@ -2159,6 +2168,11 @@ int runFilesApp(int argc, char** argv) { editLastTapDigit = -1; drawUI(); } + if (visible && mode == ViewMode::InputModal && lastTapDigit != -1 && + (tNow - lastTapMs) > 850) { + lastTapDigit = -1; + drawUI(); + } int timeoutMs = -1; if (visible && softkeyPending != 0) { @@ -2168,6 +2182,10 @@ int runFilesApp(int argc, char** argv) { int tapRem = std::max(1, static_cast(851 - (tNow - editLastTapMs))); timeoutMs = (timeoutMs < 0) ? tapRem : std::min(timeoutMs, tapRem); } + if (visible && mode == ViewMode::InputModal && lastTapDigit != -1) { + int tapRem = std::max(1, static_cast(851 - (tNow - lastTapMs))); + timeoutMs = (timeoutMs < 0) ? tapRem : std::min(timeoutMs, tapRem); + } if (visible && !statusBanner.empty() && statusBannerUntilMs > 0) { if (tNow >= statusBannerUntilMs) { statusBanner.clear(); diff --git a/src/dwm-plugins/modem_plugin.cpp b/src/dwm-plugins/modem_plugin.cpp index bca6e82..800616e 100644 --- a/src/dwm-plugins/modem_plugin.cpp +++ b/src/dwm-plugins/modem_plugin.cpp @@ -215,7 +215,7 @@ enum class AtTextMode { const char* kAtTapMap[10] = { " 0+=", - "+?=,1\"':;/-_", + "+?=,1\"':;/-_*#@$%&()[]{}", "ABC2", "DEF3", "GHI4", @@ -969,18 +969,36 @@ int runModemApp(int argc, char** argv) { (void)portNames; // Row 0: AT Command Input Box (Y=contentY..contentY+46) - drawPango(layoutSmall, &colYellow, 8, contentY, 18, - std::string(kIconTerm) + " AT (# Input mode):"); + bool focus0 = (atFocusRow == 0); int mbW = pangoTextWidth(layoutSmall, modeBadge); + if (focus0 && atLastTapKey >= 0 && atLastTapKey <= 10 && + (nowMs() - atLastTapMs) < 850) { + const char* seq = (atLastTapKey == 10) + ? "+?=,1\"':;/-_*#" + : kAtTapMap[atLastTapKey]; + std::string pref = (atLastTapKey == 10) + ? "*:" + : (std::to_string(atLastTapKey) + ":"); + std::string cand = formatMultiTapBar( + pref, seq, atLastTapIdx, atTextMode == AtTextMode::Upper, 5, 16); + drawPango(layoutSmall, &colYellow, 8, contentY, 18, cand, + kWinWidth - mbW - 18); + } else { + drawPango(layoutSmall, &colYellow, 8, contentY, 18, + std::string(kIconTerm) + " AT (# Input mode):"); + } drawPango(layoutSmall, &colGreen, kWinWidth - mbW - 8, contentY, 18, modeBadge); - bool focus0 = (atFocusRow == 0); XSetForeground(dpy, gc, focus0 ? pt.spec.bgInputFocus : pt.spec.bgAltEven); XFillRectangle(dpy, pix, gc, 6, contentY + 19, kWinWidth - 12, 26); XSetForeground(dpy, gc, focus0 ? pt.spec.accent : pt.spec.border); XDrawRectangle(dpy, pix, gc, 6, contentY + 19, kWinWidth - 13, 25); + std::string atDisp = atInput + (focus0 ? "_" : ""); + if (focus0) { + atDisp = tailFitPangoText(layoutMain, atDisp, kWinWidth - 24); + } drawPango(layoutMain, &colWhite, 12, contentY + 19, 26, - atInput + (focus0 ? "_" : ""), kWinWidth - 24); + atDisp, kWinWidth - 24); // Row 1: Preset & AT Port Selector (Y=contentY+49..contentY+73) bool focus1 = (atFocusRow == 1); @@ -2061,6 +2079,15 @@ int runModemApp(int argc, char** argv) { timeoutMs = static_cast(bRem); } } + if (activeTab == TabId::AtConsole && atLastTapKey != -1) { + int64_t tRem = (atLastTapMs + 851) - now; + if (tRem <= 0) { + atLastTapKey = -1; + render(); + } else if (timeoutMs < 0 || tRem < timeoutMs) { + timeoutMs = static_cast(tRem); + } + } // Periodic 10-second status refresh while visible on SIM/Radio tabs if (modal == ModalMode::None && activeTab != TabId::ModemLog && activeTab != TabId::AtConsole) { diff --git a/src/dwm-plugins/networks_plugin.cpp b/src/dwm-plugins/networks_plugin.cpp index 5f9c2ac..7831b20 100644 --- a/src/dwm-plugins/networks_plugin.cpp +++ b/src/dwm-plugins/networks_plugin.cpp @@ -50,7 +50,7 @@ enum class TextMode { const char* kMultiTapMap[10] = { " 0+_-", // 0 - ".,?!@-_1:/#", // 1 + kMultiTapPunct1, // 1 "abc2", // 2 "def3", // 3 "ghi4", // 4 @@ -551,7 +551,7 @@ int runNetworksApp(int argc, char** argv) { int seqLen = static_cast(std::strlen(seq)); if (seqLen <= 0) return false; - if (digit == lastTapDigit && (now - lastTapMs) < 750 && !targetStr.empty()) { + if (digit == lastTapDigit && (now - lastTapMs) <= 850 && !targetStr.empty()) { popUtf8Char(targetStr); lastTapCycle = (lastTapCycle + 1) % seqLen; } else { @@ -844,10 +844,23 @@ int runNetworksApp(int argc, char** argv) { std::string disp = showPassword ? wifiPassword : std::string(wifiPassword.size(), '*'); - drawPango(layoutMain, &colWhite, 24, 100, 34, disp + "_", kWinWidth - 48); + std::string dispFit = + tailFitPangoText(layoutMain, disp + "_", kWinWidth - 48); + drawPango(layoutMain, &colWhite, 24, 100, 34, dispFit, kWinWidth - 48); - drawPango(layoutSmall, &colMuted, 18, 144, 18, - "# Input mode", kWinWidth - 36); + if (lastTapDigit >= 0 && lastTapDigit <= 9 && + (nowMs() - lastTapMs) <= 850) { + const char* seq = (textMode == TextMode::Sym) + ? kSymTapMap[lastTapDigit] + : kMultiTapMap[lastTapDigit]; + std::string cand = formatMultiTapBar( + std::to_string(lastTapDigit) + ":", seq, lastTapCycle, + textMode == TextMode::Upper, 6, 19); + drawPango(layoutSmall, &colYellow, 18, 144, 18, cand, kWinWidth - 36); + } else { + drawPango(layoutSmall, &colMuted, 18, 144, 18, + "# Input mode", kWinWidth - 36); + } drawPango(layoutSmall, &colMuted, 18, 164, 18, "[*] Toggle Show/Hide password"); drawPango(layoutSmall, &colMuted, 18, 184, 18, @@ -860,7 +873,18 @@ int runNetworksApp(int argc, char** argv) { XSetForeground(dpy, gc, pt.spec.accent); XDrawRectangle(dpy, pix, gc, 10, 36, kWinWidth - 21, 219); - drawPango(layoutMain, &colYellow, 18, 40, 22, "Cellular APN Setup"); + if (lastTapDigit >= 0 && lastTapDigit <= 9 && + (nowMs() - lastTapMs) <= 850) { + const char* seq = (textMode == TextMode::Sym) + ? kSymTapMap[lastTapDigit] + : kMultiTapMap[lastTapDigit]; + std::string cand = formatMultiTapBar( + std::to_string(lastTapDigit) + ":", seq, lastTapCycle, + textMode == TextMode::Upper, 5, 16); + drawPango(layoutSmall, &colYellow, 18, 40, 22, cand, kWinWidth - 78); + } else { + drawPango(layoutMain, &colYellow, 18, 40, 22, "Cellular APN Setup"); + } drawPango(layoutSmall, &colBlue, kWinWidth - 58, 40, 22, std::string("[") + textModeLabel() + "]"); @@ -874,8 +898,11 @@ int runNetworksApp(int argc, char** argv) { XFillRectangle(dpy, pix, gc, 18, fy + 16, kWinWidth - 36, 24); XSetForeground(dpy, gc, isF ? pt.spec.accent : pt.spec.border); XDrawRectangle(dpy, pix, gc, 18, fy + 16, kWinWidth - 37, 23); - drawPango(layoutSmall, &colWhite, 22, fy + 16, 24, - vals[f] + (isF ? "_" : ""), kWinWidth - 44); + std::string fText = vals[f] + (isF ? "_" : ""); + if (isF) { + fText = tailFitPangoText(layoutSmall, fText, kWinWidth - 44); + } + drawPango(layoutSmall, &colWhite, 22, fy + 16, 24, fText, kWinWidth - 44); } bool saveSel = (apnFieldSel == 3); @@ -1457,6 +1484,16 @@ int runNetworksApp(int argc, char** argv) { timeoutMs = static_cast(bRem); } } + if (lastTapDigit != -1 && + (modal == NetModal::WifiPassword || modal == NetModal::ApnEditor)) { + int64_t tRem = (lastTapMs + 851) - now; + if (tRem <= 0) { + lastTapDigit = -1; + render(); + } else if (timeoutMs < 0 || tRem < timeoutMs) { + timeoutMs = static_cast(tRem); + } + } } struct pollfd pfds[2]; diff --git a/src/dwm-plugins/phone_data.h b/src/dwm-plugins/phone_data.h index 19f76cb..20d404e 100644 --- a/src/dwm-plugins/phone_data.h +++ b/src/dwm-plugins/phone_data.h @@ -2721,6 +2721,112 @@ inline bool qrtrWriteSimContacts( return outExported > 0; } +inline constexpr const char* kMultiTapPunct1 = + ".,-_!?/@:;'\"+=*#$%^&()[]{}<>|\\~`1"; +inline constexpr const char* kMultiTapStar = + "*#+-=_/\\|~`@$%^&()[]{}<>'\".,!?:;"; + +inline std::string formatMultiTapBar(const std::string& prefix, + const char* table, int activeIdx, + bool makeUpper, + int maxItems = 7, + size_t maxChars = 21) { + if (!table || !table[0]) return ""; + int total = static_cast(std::strlen(table)); + if (total <= 0) return ""; + activeIdx = std::clamp(activeIdx, 0, total - 1); + + std::vector labels; + labels.reserve(total); + for (int i = 0; i < total; ++i) { + char c = table[i]; + if (c == ' ') { + labels.push_back("SPC"); + } else if (c == '\n') { + labels.push_back("NL"); + } else if (c == '\t') { + labels.push_back("TAB"); + } else { + if (makeUpper && std::isalpha(static_cast(c))) { + c = static_cast(std::toupper(static_cast(c))); + } + labels.push_back(std::string(1, c)); + } + } + + int halfWin = std::max(1, maxItems / 2); + int start = std::max(0, activeIdx - halfWin); + int end = start; + size_t width = 0; + int count = 0; + + while (start <= activeIdx) { + width = 0; + count = 0; + end = start; + while (end < total && count < maxItems) { + size_t iw = labels[end].size() + 2; + if (count > 0 && width + iw > maxChars) break; + width += iw; + ++end; + ++count; + } + if (activeIdx < end) break; + ++start; + } + + while (start > 0 && count < maxItems) { + size_t iw = labels[start - 1].size() + 2; + if (width + iw > maxChars) break; + --start; + width += iw; + ++count; + } + + std::string out = prefix; + out += (start > 0) ? "<" : " "; + for (int i = start; i < end; ++i) { + if (i == activeIdx) { + out += "[" + labels[i] + "]"; + } else { + out += " " + labels[i] + " "; + } + } + if (end < total) { + out += ">"; + } + return out; +} + +inline std::string tailFitPangoText(PangoLayout* layout, + const std::string& text, + int maxWidthPx) { + if (!layout || text.empty() || maxWidthPx <= 0) return text; + pango_layout_set_width(layout, -1); + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE); + pango_layout_set_text(layout, text.c_str(), static_cast(text.size())); + int w = 0, h = 0; + pango_layout_get_pixel_size(layout, &w, &h); + if (w <= maxWidthPx) return text; + + size_t pos = 0; + while (pos < text.size()) { + pos++; + while (pos < text.size() && + (static_cast(text[pos]) & 0xC0) == 0x80) { + pos++; + } + std::string candidate = ".." + text.substr(pos); + pango_layout_set_text(layout, candidate.c_str(), + static_cast(candidate.size())); + pango_layout_get_pixel_size(layout, &w, &h); + if (w <= maxWidthPx) { + return candidate; + } + } + return text; +} + } // namespace bananarchy #endif // DWM_PLUGINS_PHONE_DATA_H diff --git a/src/dwm-plugins/sms_plugin.cpp b/src/dwm-plugins/sms_plugin.cpp index f19a267..079ff94 100644 --- a/src/dwm-plugins/sms_plugin.cpp +++ b/src/dwm-plugins/sms_plugin.cpp @@ -41,7 +41,7 @@ enum class TextCaseMode { const char* kMultiTapSms[10] = { " 0\n", // 0 - ".,?!'-:;()@/1", // 1 + kMultiTapPunct1, // 1 "abc2", // 2 "def3", // 3 "ghi4", // 4 @@ -776,11 +776,17 @@ int runSmsApp(int argc, char** argv) { if (!cname.empty()) { toDisp += " (" + cname + ")"; } + if (toSel && cname.empty()) { + toDisp = tailFitPangoText(layoutMain, toDisp, kWinWidth - 20); + } drawPango(layoutMain, toSel ? &colWhite : &colLight, 10, 36, 32, toDisp, kWinWidth - 20); // Field 1: Message Body bool msgSel = (composeField == 1); + bool smsTapActive = + (msgSel && lastTapDigit >= 0 && lastTapDigit <= 9 && + (nowMs() - lastTapMs) <= 850); XSetForeground(dpy, gc, msgSel ? pt.spec.bgInputFocus : pt.spec.bgInput); XFillRectangle(dpy, pix, gc, 6, 72, kWinWidth - 12, 156); @@ -789,6 +795,7 @@ int runSmsApp(int argc, char** argv) { std::string bodyDisp = composeText; if (msgSel) bodyDisp += "|"; + int bodyMaxH = smsTapActive ? 126 : 146; if (bodyDisp.empty()) { drawPango(layoutSmall, &colMuted, 12, 78, 20, "Type message (# Input mode)...", kWinWidth - 24); @@ -801,17 +808,28 @@ int runSmsApp(int argc, char** argv) { int tw = 0, th = 0; pango_layout_get_pixel_size(layoutMain, &tw, &th); int textY = 76; - if (th > 146) { - textY = 76 - (th - 146); + if (th > bodyMaxH) { + textY = 76 - (th - bodyMaxH); } XRectangle clipRect {6, 73, static_cast(kWinWidth - 12), - 154}; + static_cast(smsTapActive ? 134 : 154)}; XftDrawSetClipRectangles(xftDraw, 0, 0, &clipRect, 1); pango_xft_render_layout(xftDraw, &colWhite, layoutMain, 12 * PANGO_SCALE, textY * PANGO_SCALE); XftDrawSetClip(xftDraw, nullptr); } + if (smsTapActive) { + XSetForeground(dpy, gc, pt.spec.bgAltOdd); + XFillRectangle(dpy, pix, gc, 7, 208, kWinWidth - 14, 19); + XSetForeground(dpy, gc, pt.spec.divider); + XDrawLine(dpy, pix, gc, 7, 208, kWinWidth - 8, 208); + std::string cand = formatMultiTapBar( + std::to_string(lastTapDigit) + ":", kMultiTapSms[lastTapDigit], + lastTapCycle, caseMode == TextCaseMode::Upper); + drawPango(layoutSmall, &colYellow, 10, 208, 19, cand, kWinWidth - 20); + } + // Field 2 & 3: Send & Save Draft buttons bool sendSel = (composeField == 2); bool draftSel = (composeField == 3); @@ -956,7 +974,8 @@ int runSmsApp(int argc, char** argv) { XFillRectangle(dpy, pix, gc, 12, 80, kWinWidth - 24, 34); XSetForeground(dpy, gc, pt.spec.accentAlt); XDrawRectangle(dpy, pix, gc, 12, 80, kWinWidth - 25, 33); - std::string disp = addBlockInput + "|"; + std::string disp = + tailFitPangoText(layoutMain, addBlockInput + "|", kWinWidth - 36); drawPango(layoutMain, &colWhite, 18, 83, 28, disp, kWinWidth - 36); drawPango(layoutSmall, &colMuted, 12, 124, 20, "0-9 / * / #: Digits #: + prefix", 216); @@ -1660,6 +1679,12 @@ int runSmsApp(int argc, char** argv) { zeroHeld = false; } + if (visible && mode == ViewMode::Compose && lastTapDigit != -1 && + (tNow - lastTapMs) > 850) { + lastTapDigit = -1; + drawUI(); + } + int timeoutMs = -1; if (visible) { timeoutMs = 250; @@ -1672,6 +1697,11 @@ int runSmsApp(int argc, char** argv) { timeoutMs, std::max(1, static_cast((zeroPressMs + 420) - tNow))); } + if (mode == ViewMode::Compose && lastTapDigit != -1) { + timeoutMs = std::min( + timeoutMs, + std::max(1, static_cast(851 - (tNow - lastTapMs)))); + } } struct pollfd pfds[3]; diff --git a/src/dwm-plugins/themes_plugin.cpp b/src/dwm-plugins/themes_plugin.cpp index 7f9cbd4..f32c3e5 100644 --- a/src/dwm-plugins/themes_plugin.cpp +++ b/src/dwm-plugins/themes_plugin.cpp @@ -42,16 +42,16 @@ enum class TextCaseMode { }; const char* kMultiTapTheme[10] = { - " _-0", // 0 - ".-_1", // 1 - "abc2", // 2 - "def3", // 3 - "ghi4", // 4 - "jkl5", // 5 - "mno6", // 6 - "pqrs7", // 7 - "tuv8", // 8 - "wxyz9" // 9 + " _-0", // 0 + ".-_1,!?+@#$%~()[]{}", // 1 + "abc2", // 2 + "def3", // 3 + "ghi4", // 4 + "jkl5", // 5 + "mno6", // 6 + "pqrs7", // 7 + "tuv8", // 8 + "wxyz9" // 9 }; const char* kHexKeyMap[10] = { @@ -821,13 +821,22 @@ int runThemesApp(int argc, char** argv) { XFillRectangle(dpy, pix, gc, 10, 74, kWinWidth - 20, 38); XSetForeground(dpy, gc, pt.spec.accentAlt); XDrawRectangle(dpy, pix, gc, 10, 74, kWinWidth - 21, 37); - drawPango(layoutMain, &colWhite, 16, 74, 38, nameInput + "|", - kWinWidth - 32); + std::string nameDisp = + tailFitPangoText(layoutMain, nameInput + "|", kWinWidth - 32); + drawPango(layoutMain, &colWhite, 16, 74, 38, nameDisp, kWinWidth - 32); drawPango(layoutSmall, &colMuted, 10, 124, 20, "Saves to ~/.xthemes/.xth", 220); - drawPango(layoutSmall, &colMuted, 10, 146, 20, - "# Input mode", 220); + if (lastTapDigit >= 0 && lastTapDigit <= 9 && + (nowMs() - lastTapMs) <= 850) { + std::string cand = formatMultiTapBar( + std::to_string(lastTapDigit) + ":", kMultiTapTheme[lastTapDigit], + lastTapCycle, caseMode == TextCaseMode::Upper); + drawPango(layoutSmall, &colYellow, 10, 146, 20, cand, 220); + } else { + drawPango(layoutSmall, &colMuted, 10, 146, 20, + "# Input mode", 220); + } drawSoftkeyBar("Save", "Save", nameInput.empty() ? "Cancel" : "Del"); } else if (mode == ViewMode::ConfirmDelete && !themes.empty()) { @@ -1323,10 +1332,20 @@ int runThemesApp(int argc, char** argv) { } } + if (visible && mode == ViewMode::NameModal && lastTapDigit != -1 && + (tNow - lastTapMs) > 850) { + lastTapDigit = -1; + drawUI(); + } + int timeoutMs = -1; if (visible && softkeyPending != 0) { timeoutMs = std::max(1, static_cast(softkeyDeadlineMs - tNow)); } + if (visible && mode == ViewMode::NameModal && lastTapDigit != -1) { + int tapRem = std::max(1, static_cast(851 - (tNow - lastTapMs))); + timeoutMs = (timeoutMs < 0) ? tapRem : std::min(timeoutMs, tapRem); + } struct pollfd pfds[2]; int nfds = 0; diff --git a/src/fcitx5-t9/src/t9.cpp b/src/fcitx5-t9/src/t9.cpp index e6e567a..e752117 100644 --- a/src/fcitx5-t9/src/t9.cpp +++ b/src/fcitx5-t9/src/t9.cpp @@ -975,18 +975,23 @@ void T9State::showPreview(const std::vector& items, int index) { if (total == 0) { return; } + index = std::clamp(index, 0, total - 1); constexpr int kMaxItemsPerPage = 7; std::string prefix = modePrefix() + ":"; size_t maxItemsWidth = - (kFixedPreeditChars > prefix.size() + 1) - ? (kFixedPreeditChars - prefix.size() - 1) - : 23; + (kFixedPreeditChars > prefix.size() + 2) + ? (kFixedPreeditChars - prefix.size() - 2) + : 21; - int start = 0; - int end = 0; - while (start < total) { - size_t width = 0; - int count = 0; + int halfWin = kMaxItemsPerPage / 2; + int start = std::max(0, index - halfWin); + int end = start; + size_t width = 0; + int count = 0; + + while (start <= index) { + width = 0; + count = 0; end = start; while (end < total && count < kMaxItemsPerPage) { size_t itemWidth = items[end].label.size() + 2; @@ -1000,10 +1005,21 @@ void T9State::showPreview(const std::vector& items, int index) { if (index < end) { break; } - start = end; + ++start; + } + + while (start > 0 && count < kMaxItemsPerPage) { + size_t itemWidth = items[start - 1].label.size() + 2; + if (width + itemWidth > maxItemsWidth) { + break; + } + --start; + width += itemWidth; + ++count; } std::string preedit = prefix; + preedit += (start > 0) ? "<" : " "; for (int i = start; i < end; ++i) { if (i == index) { preedit += "["; @@ -1015,8 +1031,8 @@ void T9State::showPreview(const std::vector& items, int index) { preedit += " "; } } - if (start > 0 || end < total) { - preedit += (end < total) ? ">" : "<"; + if (end < total) { + preedit += ">"; } if (preedit.size() < kFixedPreeditChars) { preedit.append(kFixedPreeditChars - preedit.size(), ' ');