diff --git a/overlay/rootfs/usr/bin/sxmo_modemmonitor.sh b/overlay/rootfs/usr/bin/sxmo_modemmonitor.sh index f4806d2..e2f0593 100755 --- a/overlay/rootfs/usr/bin/sxmo_modemmonitor.sh +++ b/overlay/rootfs/usr/bin/sxmo_modemmonitor.sh @@ -115,8 +115,8 @@ mainloop() { esac if [ "$CHANGED" = "1" ]; then - # Fetch message (third argument) in sxmo_ussd.sh to save time in this loop - sxmo_terminal.sh sxmo_ussd.sh "$MODEM" "$TYPE" "" & + # Handle unsolicited USSD in background without spawning a terminal window + sxmo_ussd.sh "$MODEM" "$TYPE" "" & fi done & diff --git a/overlay/rootfs/usr/bin/sxmo_ussd.sh b/overlay/rootfs/usr/bin/sxmo_ussd.sh new file mode 100755 index 0000000..297733b --- /dev/null +++ b/overlay/rootfs/usr/bin/sxmo_ussd.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright 2022 Sxmo Contributors + +# include common definitions +# shellcheck source=scripts/core/sxmo_common.sh +. sxmo_common.sh + +MODEM="$1" +TYPE="$2" +MESSAGE="${3:-}" + +# If xdialer, xmodem, or msm-modem-uim-selection ussd is actively handling USSD, +# do not interfere with the active USSD session. +RDIR="${XDG_RUNTIME_DIR:-/tmp}" +if [ -f "$RDIR/xdialer.pid" ] || [ -f "$RDIR/xmodem.pid" ] || \ + pgrep -f "msm-modem-uim-selection ussd" >/dev/null 2>&1; then + exit 0 +fi + +MM_IFACE="org.freedesktop.ModemManager1" +DBUS_PROP_IFACE="org.freedesktop.DBus.Properties" +USSD_IFACE="org.freedesktop.ModemManager1.Modem.Modem3gpp.Ussd" + +if [ "$TYPE" = "NetworkNotification" ] || [ "$TYPE" = "NetworkRequest" ]; then + MESSAGE="$(busctl --json=short call "$MM_IFACE" "$MODEM" "$DBUS_PROP_IFACE" Get ss "$USSD_IFACE" "$TYPE" 2>/dev/null | jq -r '.data[].data // empty' 2>/dev/null || true)" +fi + +if [ -z "$MESSAGE" ]; then + exit 0 +fi + +sxmo_notify_user.sh "USSD: $MESSAGE" diff --git a/overlay/rootfs/usr/lib/dwm/plugins/dialer.so b/overlay/rootfs/usr/lib/dwm/plugins/dialer.so index 9cb42be..838980b 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/dialer.so and b/overlay/rootfs/usr/lib/dwm/plugins/dialer.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/modem.so b/overlay/rootfs/usr/lib/dwm/plugins/modem.so index 3b0a83e..320ea0a 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/libexec/msm-modem-uim-selection b/overlay/rootfs/usr/libexec/msm-modem-uim-selection index 4b0158a..87985dd 100755 --- a/overlay/rootfs/usr/libexec/msm-modem-uim-selection +++ b/overlay/rootfs/usr/libexec/msm-modem-uim-selection @@ -1014,23 +1014,80 @@ case "$cmd" in echo "ERROR: Empty USSD code" exit 1 fi - mm_state="$(mmcli -m any -K 2>/dev/null | awk -F': ' '$1 == "modem.generic.state" {print $2; exit}')" + mm_state="$(mmcli -m any -K 2>/dev/null | awk -F': ' '$1 == "modem.generic.state" {print $2; exit}' || true)" if [ "$mm_state" = "disabled" ] || [ -z "$mm_state" ]; then mmcli -m any -e >/dev/null 2>&1 || true sleep 1 fi + modem_dbus_path="$(mmcli -m any -K 2>/dev/null | awk -F': ' '$1 == "modem.dbus-path" {print $2; exit}' || true)" + get_ussd_prop() { + prop_name="$1" + if [ -n "$modem_dbus_path" ] && command -v busctl >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then + busctl --json=short call org.freedesktop.ModemManager1 "$modem_dbus_path" \ + org.freedesktop.DBus.Properties Get ss \ + org.freedesktop.ModemManager1.Modem.Modem3gpp.Ussd "$prop_name" 2>/dev/null | \ + jq -r '.data[].data // empty' 2>/dev/null || true + fi + } + prev_notif="$(get_ussd_prop NetworkNotification)" + prev_req="$(get_ussd_prop NetworkRequest)" + ussd_st="$(mmcli -m any -K 2>/dev/null | awk -F': ' '$1 == "modem.3gpp.ussd.status" || $1 == "modem.3gpp.ussd.state" {print $2; exit}' || true)" + if [ "$ussd_mode" = "respond" ] && [ "$ussd_st" != "user-response" ]; then + case "$ussd_code" in + \**\#|\#*\#) + ussd_mode="initiate" + ;; + esac + fi + mm_rc=0 + mm_out="" if [ "$ussd_mode" = "respond" ]; then - mm_out="$(mmcli -m any --3gpp-ussd-respond="$ussd_code" 2>&1)" - mm_rc=$? + mm_out="$(mmcli --timeout=30 -m any --3gpp-ussd-respond="$ussd_code" 2>&1)" || mm_rc=$? else - mmcli -m any --3gpp-ussd-cancel >/dev/null 2>&1 || true - mm_out="$(mmcli -m any --3gpp-ussd-initiate="$ussd_code" 2>&1)" - mm_rc=$? + if [ "$ussd_st" = "active" ] || [ "$ussd_st" = "user-response" ]; then + mmcli -m any --3gpp-ussd-cancel >/dev/null 2>&1 || true + fi + mm_out="$(mmcli --timeout=30 -m any --3gpp-ussd-initiate="$ussd_code" 2>&1)" || mm_rc=$? fi if [ "$mm_rc" -eq 0 ] && [ -n "$mm_out" ]; then - printf "%s\n" "$mm_out" | sed -e "s/^USSD session initiated; new reply from network: '\(.*\)'$/\1/" -e "s/^USSD response sent; new reply from network: '\(.*\)'$/\1/" - exit 0 + reply="$(printf "%s\n" "$mm_out" | sed \ + -e "1s/^USSD session initiated; new reply from network: '//" \ + -e "1s/^response successfully sent in USSD session; new reply from network: '//" \ + -e "1s/^USSD response sent; new reply from network: '//" \ + -e "\$s/'$//")" + if [ -n "$reply" ]; then + printf "%s\n" "$reply" + exit 0 + fi fi + # Check if the USSD response arrived asynchronously via D-Bus properties + w=0 + while [ "$w" -lt 25 ]; do + cur_req="$(get_ussd_prop NetworkRequest)" + cur_notif="$(get_ussd_prop NetworkNotification)" + if [ -n "$cur_req" ] && [ "$cur_req" != "$prev_req" ]; then + printf "%s\n" "$cur_req" + exit 0 + fi + if [ -n "$cur_notif" ] && [ "$cur_notif" != "$prev_notif" ]; then + printf "%s\n" "$cur_notif" + exit 0 + fi + cur_st="$(mmcli -m any -K 2>/dev/null | awk -F': ' '$1 == "modem.3gpp.ussd.status" || $1 == "modem.3gpp.ussd.state" {print $2; exit}' || true)" + if [ "$cur_st" != "active" ] && [ "$w" -ge 8 ]; then + if [ -n "$cur_req" ]; then + printf "%s\n" "$cur_req" + exit 0 + fi + if [ -n "$cur_notif" ]; then + printf "%s\n" "$cur_notif" + exit 0 + fi + break + fi + sleep 0.2 + w=$((w + 1)) + done # Fallback to direct AT+CUSD on the active SIM AT port if [ "$PREFERRED_SIM_SLOT" = "2" ]; then at_candidates="/dev/wwan0at1 /dev/wwan0at0" diff --git a/overlay/rootfs/usr/sbin/ModemManager b/overlay/rootfs/usr/sbin/ModemManager index 26c4fb4..ed356e3 100755 Binary files a/overlay/rootfs/usr/sbin/ModemManager and b/overlay/rootfs/usr/sbin/ModemManager differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so index 9cb42be..838980b 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.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 3b0a83e..320ea0a 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/src/dwm-plugins/modem_plugin.cpp b/src/dwm-plugins/modem_plugin.cpp index bf9b8b0..a651657 100644 --- a/src/dwm-plugins/modem_plugin.cpp +++ b/src/dwm-plugins/modem_plugin.cpp @@ -569,6 +569,7 @@ int runModemApp(int argc, char** argv) { std::string ussdInput = "*100#"; std::string ussdResponse; bool ussdWaitingReply = false; + int ussdScrollLine = 0; // AT Console state int atFocusRow = 0; // 0 = Input, 1 = Preset/Port, 2 = Output scroll @@ -1180,10 +1181,28 @@ int runModemApp(int argc, char** argv) { : ussdResponse; pango_layout_set_width(layoutSmall, (kWinWidth - 48) * PANGO_SCALE); pango_layout_set_wrap(layoutSmall, PANGO_WRAP_WORD_CHAR); + pango_layout_set_ellipsize(layoutSmall, PANGO_ELLIPSIZE_NONE); pango_layout_set_text(layoutSmall, respText.c_str(), static_cast(respText.size())); - pango_xft_render_layout(xftDraw, &colLight, layoutSmall, - 24 * PANGO_SCALE, 116 * PANGO_SCALE); + int lineCount = pango_layout_get_line_count(layoutSmall); + int maxVisLines = 6; + if (ussdScrollLine > std::max(0, lineCount - maxVisLines)) { + ussdScrollLine = std::max(0, lineCount - maxVisLines); + } + int curY = 114; + for (int li = ussdScrollLine; + li < lineCount && (li - ussdScrollLine) < maxVisLines; ++li) { + PangoLayoutLine* pl = pango_layout_get_line_readonly(layoutSmall, li); + if (pl) { + PangoRectangle ink{}, logical{}; + pango_layout_line_get_pixel_extents(pl, &ink, &logical); + pango_xft_render_layout_line( + xftDraw, &colLight, pl, 24 * PANGO_SCALE, + (curY - logical.y) * PANGO_SCALE); + curY += std::max(18, logical.height + 2); + } + } + pango_layout_set_width(layoutSmall, -1); drawSoftkeyBar("Send", "Send", "Close"); } @@ -1414,11 +1433,16 @@ int runModemApp(int argc, char** argv) { "\nIMEI 2: " + (im.imei2.empty() ? "--" : im.imei2) + "\nIMEI SV: " + (im.imeiSv.empty() ? "--" : im.imeiSv); ussdWaitingReply = false; + ussdScrollLine = 0; return; } - ussdResponse = "Sending USSD " + ussdInput + "..."; + bool wasWaiting = ussdWaitingReply; + std::string req = ussdInput; + ussdScrollLine = 0; + ussdResponse = "Sending USSD " + req + "..."; render(); - ussdResponse = sendModemUssd(ussdInput, ussdWaitingReply, &ussdWaitingReply); + ussdResponse = sendModemUssd(req, wasWaiting && !isUssdCode(req), + &ussdWaitingReply); if (ussdWaitingReply) { ussdInput.clear(); } @@ -1765,10 +1789,29 @@ int runModemApp(int argc, char** argv) { } continue; } + if (sym == XK_Up || sym == XK_KP_Up) { + if (ussdScrollLine > 0) { + --ussdScrollLine; + render(); + } + continue; + } + if (sym == XK_Down || sym == XK_KP_Down) { + ++ussdScrollLine; + render(); + continue; + } if (sym == XK_BackSpace || sym == XK_Delete) { if (!ussdInput.empty()) { ussdInput.pop_back(); render(); + } else { + if (ussdWaitingReply) { + cancelModemUssd(); + ussdWaitingReply = false; + } + modal = ModalMode::None; + render(); } continue; } diff --git a/src/dwm-plugins/phone_data.h b/src/dwm-plugins/phone_data.h index 3c997c7..c0ce983 100644 --- a/src/dwm-plugins/phone_data.h +++ b/src/dwm-plugins/phone_data.h @@ -1670,7 +1670,7 @@ inline std::string sendModemUssd(const std::string& input, if (outSessionActive) { std::string stOut = runCaptureCmd( - "mmcli -m any -K 2>/dev/null | grep 'modem.3gpp.ussd.state'"); + "mmcli -m any -K 2>/dev/null | grep -E 'modem\\.3gpp\\.ussd\\.(status|state)'"); if (stOut.find("user-response") != std::string::npos) { *outSessionActive = true; }