Fix SIM2 QMI UIM provisioning, auto-enable ModemManager, PIN auto-prompt, call audio, and Dunst clearing

This commit is contained in:
Luxferre
2026-10-25 11:31:41 +02:00
parent 9b712dcbca
commit b56f310f21
41 changed files with 1193 additions and 352 deletions
@@ -1,7 +1,7 @@
# Maximum time to wait for SIM card to appear after starting the modem.
# Some SIM cards or modem firmwares might take longer to initialize.
# Set to 0 to disable waiting entirely.
sim_wait_time=4
sim_wait_time=10
# Preferred physical SIM slot (1 or 2) for Primary GW provisioning on Dual-SIM
# Qualcomm MSM8905/MSM8909 devices. Falls back automatically if the preferred
+21
View File
@@ -0,0 +1,21 @@
#!/sbin/openrc-run
# This script tries to automatically configure UIM application for Qualcomm QMI
# modems with more than one sim slot.
name="UIM Slot selection"
description="Select UIM slot and application on the embedded QMI modem."
depend() {
need rmtfs
after msm8909-modem-wwan-port
before ofono
before modemmanager
}
# Use the service start method instead of command= because we want to block
# other services while the modem isn't ready yet.
start() {
SIM_WAIT_TIME="${sim_wait_time:-10}" /usr/libexec/msm-modem-uim-selection boot
eend $?
}
@@ -8,7 +8,7 @@
horizontal_padding = 6
frame_width = 2
frame_color = "#005577"
idle_threshold = 120
idle_threshold = 0
font = Fira Code 9.5
markup = full
max_icon_size = 24
@@ -22,16 +22,16 @@
[urgency_low]
background = "#222222"
foreground = "#cccccc"
timeout = 8
timeout = 5
[urgency_normal]
background = "#1c1c1c"
foreground = "#ffffff"
frame_color = "#005577"
timeout = 12
timeout = 8
[urgency_critical]
background = "#900000"
foreground = "#ffffff"
frame_color = "#ff0000"
timeout = 0
timeout = 10
+24 -43
View File
@@ -3,61 +3,42 @@
# shellcheck source=scripts/core/sxmo_common.sh
. sxmo_common.sh
ca_dbus_get_prop() {
dbus-send --session --print-reply --dest=org.mobian_project.CallAudio \
--reply-timeout=2500 /org/mobian_project/CallAudio org.freedesktop.DBus.Properties.Get \
string:org.mobian_project.CallAudio string:"$1"
}
ca_dbus_set_prop() {
dbus-send --session --print-reply --type=method_call \
--reply-timeout=2500 --dest=org.mobian_project.CallAudio \
/org/mobian_project/CallAudio org.mobian_project.CallAudio."$1" "$2" |\
grep -q "boolean true" && return 0 || return 1
}
enable_call_audio_mode() {
if pactl -f json info 2>/dev/null | jq -r .server_name | grep -q PipeWire; then
sxmo_audio.sh device set Earpiece
sxmo_log "Successfully enabled call audio mode (Earpiece)."
sxmo_hook_statusbar.sh volume
if command -v amixer >/dev/null 2>&1; then
amixer -c 0 sset 'PRI_MI2S_RX Voice Mixer VoiceMMode1' on >/dev/null 2>&1 || true
amixer -c 0 sset 'VoiceMMode1 Capture Mixer TERT_MI2S_TX' on >/dev/null 2>&1 || true
amixer -c 0 sset 'RX1 MIX1 INP1' 'RX1' >/dev/null 2>&1 || true
amixer -c 0 sset 'RDAC2 MUX' 'RX1' >/dev/null 2>&1 || true
amixer -c 0 sset 'EAR_S' 'Switch' >/dev/null 2>&1 || true
amixer -c 0 sset 'DEC1 MUX' 'ADC1' >/dev/null 2>&1 || true
amixer -c 0 sset 'ADC1 Volume' 100% >/dev/null 2>&1 || true
amixer -c 0 sset 'RX1 Digital Volume' 84 >/dev/null 2>&1 || true
fi
sxmo_audio.sh device set Earpiece >/dev/null 2>&1 || true
sxmo_log "Enabled ALSA voice call audio mode (Earpiece)."
sxmo_hook_statusbar.sh volume >/dev/null 2>&1 || true
return 0
fi
if ca_dbus_set_prop SelectMode uint32:1; then
sxmo_log "Successfully enabled call audio mode."
sxmo_hook_statusbar.sh volume
else
sxmo_notify_user.sh "Failed to enable call audio mode."
return 1
fi
}
disable_call_audio_mode() {
if pactl -f json info 2>/dev/null | jq -r .server_name | grep -q PipeWire; then
sxmo_audio.sh device set Speaker
sxmo_log "Successfully disabled call audio mode (Speaker)."
sxmo_hook_statusbar.sh volume
if command -v amixer >/dev/null 2>&1; then
amixer -c 0 sset 'PRI_MI2S_RX Voice Mixer VoiceMMode1' off >/dev/null 2>&1 || true
amixer -c 0 sset 'VoiceMMode1 Capture Mixer TERT_MI2S_TX' off >/dev/null 2>&1 || true
fi
sxmo_audio.sh device set Speaker >/dev/null 2>&1 || true
sxmo_log "Disabled ALSA voice call audio mode (Speaker)."
sxmo_hook_statusbar.sh volume >/dev/null 2>&1 || true
return 0
fi
if ca_dbus_set_prop SelectMode uint32:0; then
sxmo_log "Successfully disabled call audio mode."
sxmo_hook_statusbar.sh volume
else
sxmo_notify_user.sh "Failed to disable call audio mode."
return 1
fi
}
setup_audio() {
if ! enable_call_audio_mode; then
return 1
fi
enable_call_audio_mode
return 0
}
reset_audio() {
if ! disable_call_audio_mode; then
return 1
fi
disable_call_audio_mode
return 0
}
"$@"
+5 -3
View File
@@ -36,7 +36,7 @@ pickup() {
NUM="$(vid_to_number "$CALLID" 2>/dev/null || true)"
DIRECTION="$(
mmcli --voice-status -o "$CALLID" -K |
(mmcli -m any -o "$CALLID" -K 2>/dev/null || mmcli -o "$CALLID" -K 2>/dev/null || mmcli --voice-status -o "$CALLID" -K 2>/dev/null) |
grep call.properties.direction |
cut -d: -f2 |
tr -d " "
@@ -53,11 +53,12 @@ pickup() {
return 1
fi
sxmo_notify_user.sh "Started call"
touch "$XDG_RUNTIME_DIR/sxmo_calls/${CALLID}.initiatedcall"
log_event "call_start" "$CALLID"
if command -v xcallscreen >/dev/null 2>&1; then
xcallscreen outgoing "$CALLID" "$NUM" >/dev/null 2>&1 || true
else
sxmo_notify_user.sh "Started call"
fi
;;
incoming)
@@ -74,11 +75,12 @@ pickup() {
return 1
fi
sxmo_notify_user.sh "Picked up call"
touch "$XDG_RUNTIME_DIR/sxmo_calls/${CALLID}.pickedupcall"
log_event "call_pickup" "$CALLID"
if command -v xcallscreen >/dev/null 2>&1; then
xcallscreen active "$CALLID" "$NUM" >/dev/null 2>&1 || true
else
sxmo_notify_user.sh "Picked up call"
fi
;;
*)
+23 -16
View File
@@ -16,15 +16,16 @@ dial_number() {
return 0
fi
NUMBER="$1"
NUMBER="$(printf "%s" "$1" | tr -cd '+0-9*#')"
if [ -z "$NUMBER" ]; then
return 1
fi
CLEANEDNUMBER="$(pnc find ${DEFAULT_COUNTRY:+-c "$DEFAULT_COUNTRY"} "$1" 2>/dev/null || true)"
if [ -n "$CLEANEDNUMBER" ] && [ "$NUMBER" != "$CLEANEDNUMBER" ]; then
NUMBER="$(cat <<EOF | sxmo_dmenu.sh -p "Rewrite ?"
$NUMBER
$CLEANEDNUMBER
EOF
)"
mm_state="$(mmcli -m any -K 2>/dev/null | awk -F': ' '$1 == "modem.generic.state" {print $2; exit}')"
if [ "$mm_state" = "disabled" ] || [ -z "$mm_state" ]; then
mmcli -m any -e >/dev/null 2>&1 || true
mmcli -m any --3gpp-register-home >/dev/null 2>&1 || true
sleep 1
fi
sxmo_log "Attempting to dial: $NUMBER"
@@ -33,7 +34,7 @@ EOF
fi
if ! CALLID="$(
mmcli -m any --voice-create-call "number=$NUMBER" |
mmcli -m any --voice-create-call="number=$NUMBER" 2>/dev/null |
grep -Eo "Call/[0-9]+" |
grep -oE "[0-9]+"
)"; then
@@ -79,16 +80,22 @@ EOF
}
dial_ussd() {
USSD="$1"
DBUS_MODEM=$(mmcli -J -L | jq -r '."modem-list"[]')
sxmo_log "Dialling USSD: $USSD"
OUTPUT="$(doas -n /usr/libexec/msm-modem-uim-selection ussd initiate "$USSD" 2>/dev/null || /usr/libexec/msm-modem-uim-selection ussd initiate "$USSD" 2>/dev/null || true)"
if [ -z "$OUTPUT" ]; then
DBUS_MODEM="$(mmcli -J -L 2>/dev/null | jq -r '."modem-list"[]' 2>/dev/null | head -n1)"
MM_BUS="org.freedesktop.ModemManager1"
USSD_IFACE="org.freedesktop.ModemManager1.Modem.Modem3gpp.Ussd"
sxmo_log "Dialling USSD: $USSD"
OUTPUT=$(busctl --json=short call "$MM_BUS" "$DBUS_MODEM" "$USSD_IFACE" Initiate "s" "$USSD" | jq -r '.data[]')
sxmo_terminal.sh sxmo_ussd.sh "$DBUS_MODEM" "Unknown" "$OUTPUT" &
if [ -n "$DBUS_MODEM" ]; then
OUTPUT="$(busctl --json=short call "$MM_BUS" "$DBUS_MODEM" "$USSD_IFACE" Initiate "s" "$USSD" 2>/dev/null | jq -r '.data[]' 2>/dev/null || true)"
fi
fi
if [ -n "$OUTPUT" ]; then
sxmo_notify_user.sh "USSD ($USSD): $OUTPUT"
else
sxmo_notify_user.sh --urgency=critical "USSD request failed ($USSD)"
fi
}
dial_menu() {
+174
View File
@@ -0,0 +1,174 @@
#!/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
# see networkmanager documentation for these state names
statenumtoname() {
case "$1" in
"int32 -1") printf "failed" ;;
"int32 0") printf "unknown" ;;
"int32 1") printf "initializing" ;;
"int32 2") printf "locked" ;;
"int32 3") printf "disabled" ;;
"int32 4") printf "disabling" ;;
"int32 5") printf "enabling" ;;
"int32 6") printf "enabled" ;;
"int32 7") printf "searching" ;;
"int32 8") printf "registered" ;;
"int32 9") printf "disconnecting" ;;
"int32 10") printf "connecting" ;;
"int32 11") printf "connected" ;;
*) printf "ERROR" ;;
esac
}
mainloop() {
# get initial modem state and handle SIM2/QMI PIN lock or unprovisioned slot
(
tries=0
newstate=""
while [ "$tries" -lt 15 ]; do
newstate="$(mmcli -m any -K 2>/dev/null | grep "^modem.generic.state " | cut -d':' -f2 | sed 's/^ //')"
if [ -n "$newstate" ]; then
break
fi
sleep 2
tries=$((tries + 1))
done
uim_st="$(doas -n /usr/libexec/msm-modem-uim-selection status 2>/dev/null || /usr/libexec/msm-modem-uim-selection status 2>/dev/null || true)"
if printf "%s\n" "$uim_st" | grep -qE "SLOT[12]_PIN1_STATE=(enabled-not-verified|blocked)|SLOT[12]_APP_STATE=(pin1|puk1)"; then
newstate="locked"
elif printf "%s\n" "$uim_st" | grep -qE "SLOT[12]_STATE=present" && printf "%s\n" "$uim_st" | grep -q "^PRIMARY_GW_SLOT=0$"; then
doas -n /usr/libexec/msm-modem-uim-selection boot >/dev/null 2>&1 || true
newstate="$(mmcli -m any -K 2>/dev/null | grep "^modem.generic.state " | cut -d':' -f2 | sed 's/^ //')"
fi
if [ -n "$newstate" ]; then
# fake oldstate (boot) and reason (0)
sxmo_hook_modem.sh "boot" "$newstate" "0"
sxmo_hook_statusbar.sh modem
fi
) &
# Monitor for incoming calls
dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Voice',type='signal',member='CallAdded'" |
while read -r line; do
echo "$line" | grep -qE "^signal" && sxmo_modem.sh checkforincomingcalls
done &
# Monitor for incoming texts
dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Messaging',type='signal',member='Added'" |
while read -r line; do
echo "$line" | grep -qE "^signal" && sxmo_modem.sh checkfornewtexts
done &
# Check for new texts periodically
sxmo_run_aligned.sh 300 sxmo_modem.sh checkfornewtexts &
# Monitor for finished calls
dbus-monitor --system "interface='org.freedesktop.DBus.Properties',member='PropertiesChanged',arg0='org.freedesktop.ModemManager1.Call'" |
while read -r line; do
echo "$line" | grep -qE "^signal" && sxmo_modem.sh checkforfinishedcalls
done &
# Periodically remove finished calls
sxmo_run_aligned.sh 300 sxmo_modem.sh checkforfinishedcalls &
# Monitor for modem state change
dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem',type='signal',member='StateChanged'" |
while read -r line; do
if echo "$line" | grep -qE "^signal.*StateChanged"; then
read -r oldstate
read -r newstate
read -r reason
sxmo_hook_modem.sh "$(statenumtoname "$oldstate")" \
"$(statenumtoname "$newstate")" \
"$(echo "$reason" | sed 's/uint32 //')"
sxmo_hook_statusbar.sh modem
fi
done &
# Monitor for 3GPP USSD
dbus-monitor --system "interface='org.freedesktop.DBus.Properties',member='PropertiesChanged',arg0='org.freedesktop.ModemManager1.Modem.Modem3gpp.Ussd'" |
while read -r line; do
case "$line" in
*"/org/freedesktop/ModemManager1/Modem"*)
MODEM=$(echo "$line" | awk -F'[;=]' '{print $6}')
CHANGED=0
;;
*"string \"NetworkNotification"*)
TYPE="NetworkNotification"
CHANGED=1
;;
*"string \"NetworkRequest"*)
TYPE="NetworkRequest"
CHANGED=1
;;
*)
CHANGED=0
;;
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" "" &
fi
done &
if [ -f "${SXMO_MMS_BASE_DIR:-"$HOME"/.mms/modemmanager}/mms" ]; then
# monitor for received mms
dbus-monitor "interface='org.ofono.mms.Service',type='signal',member='MessageAdded'" |
while read -r line; do
if echo "$line" | grep -q '^object path'; then
MESSAGE_PATH="$(echo "$line" | cut -d\" -f2)"
fi
if echo "$line" | grep -q 'string "received"'; then
sxmo_mms.sh processmms "$MESSAGE_PATH"
fi
done &
fi
if [ -f "${SXMO_VVM_BASE_DIR:-"$HOME"/.vvm/modemmanager}/vvm" ]; then
# monitor for vvm (Visual Voice Mail)
VVM_START=0
dbus-monitor "interface='org.kop316.vvm.Service',type='signal',member='MessageAdded'" |
while read -r line; do
if echo "$line" | grep -q '^object path'; then
VVM_ID="$(echo "$line" | cut -d\" -f2 | rev | cut -d'/' -f1 | rev)"
VVM_START=1
fi
if echo "$line" | grep -q '^]'; then
VVM_START=0
sxmo_vvm.sh processvvm "$VVM_DATE" "$VVM_SENDER" "$VVM_ID" "$VVM_ATTACHMENT"
fi
if [ "$VVM_START" -eq 1 ]; then
if echo "$line" | grep -q '^string "Date"'; then
read -r line
VVM_DATE="$(echo "$line" | cut -d\" -f2)"
elif echo "$line" | grep -q '^string "Sender"'; then
read -r line
VVM_SENDER="$(echo "$line" | cut -d\" -f2)"
elif echo "$line" | grep -q '^string "Attachments"'; then
read -r line
VVM_ATTACHMENT="$(echo "$line" | cut -d\" -f2)"
fi
fi
done &
fi
wait
}
# new session, clean up all phone related files
rm "$XDG_RUNTIME_DIR/sxmo_calls/"* 2>/dev/null || true
rm -f "$XDG_RUNTIME_DIR"/sxmo.ring.pid 2>/dev/null
rm -f "$SXMO_NOTIFDIR"/incomingcall* 2>/dev/null
mainloop
+55 -35
View File
@@ -12,39 +12,50 @@ SXMO_NOTIFDIR="${SXMO_NOTIFDIR:-${XDG_DATA_HOME:-$HOME/.local/share}/sxmo/notifi
[ -z "$SXMO_NOTIFDIR" ] || [ "$SXMO_NOTIFDIR" = "/" ] && SXMO_NOTIFDIR="$HOME/.local/share/sxmo/notifications"
_clear_notif_group() {
find "$SXMO_NOTIFDIR" -type f | while read -r file; do
if awk NR==1 "$file" | grep -lxq "$1"; then
rm "$file"
find "$SXMO_NOTIFDIR" -type f 2>/dev/null | while read -r file; do
if [ -f "$file" ] && awk NR==1 "$file" 2>/dev/null | grep -lxq "$1"; then
rm -f "$file"
fi
done
}
_handle_new_notif_file(){
file="$1"
clear_all() {
rm -f "$SXMO_NOTIFDIR"/* 2>/dev/null || true
dunstctl close-all >/dev/null 2>&1 || true
dunstctl history-clear >/dev/null 2>&1 || true
pkill -x dunstify >/dev/null 2>&1 || true
sxmo_hook_statusbar.sh notifications >/dev/null 2>&1 || true
}
if [ "$(wc -l "$file" | cut -d' ' -f1)" -lt 3 ]; then
_handle_new_notif_file() {
file="$1"
[ -f "$file" ] || return 0
if [ "$(wc -l < "$file" 2>/dev/null || echo 0)" -lt 3 ]; then
sxmo_log "Invalid notification file $file found, deleting!"
rm -f "$file"
return
return 0
fi
sxmo_hook_notification.sh "$file" &
group="$(awk NR==1 "$file")"
action="$(awk NR==2 "$file")"
msg="$(tail -n+3 "$file" | cut -c1-70)"
group="$(awk NR==1 "$file" 2>/dev/null)"
action="$(awk NR==2 "$file" 2>/dev/null)"
msg="$(tail -n+3 "$file" 2>/dev/null | cut -c1-70)"
case "$(dunstify --action="default,Open" "$msg")" in
(
case "$(dunstify --action="default,Open" "$msg" 2>/dev/null)" in
"2")
#
# _clear_notif_group "$group"
#
;;
"default")
_clear_notif_group "$group"
sxmo_hook_statusbar.sh notifications >/dev/null 2>&1 || true
if [ -n "$action" ] && [ "$action" != "none" ]; then
setsid -f sh -c "$action" > /dev/null 2>&1
fi
;;
esac
) &
}
_notifications_hook() {
@@ -58,7 +69,7 @@ _notifications_hook() {
monitor() {
mkdir -p "$SXMO_NOTIFDIR"
find "$SXMO_NOTIFDIR" -type f | while read -r file; do
find "$SXMO_NOTIFDIR" -type f 2>/dev/null | while read -r file; do
_handle_new_notif_file "$file"
done
@@ -66,18 +77,18 @@ monitor() {
fifo="$(mktemp -u)"
mkfifo "$fifo"
inotifywait -mq -e attrib,move,delete "$SXMO_NOTIFDIR" >> "$fifo" &
inotifywait -mq -e close_write,moved_to,delete "$SXMO_NOTIFDIR" >> "$fifo" &
inotif_pid=$!
finish() {
kill "$inotif_pid"
rm "$fifo"
kill "$inotif_pid" 2>/dev/null || true
rm -f "$fifo"
exit
}
trap 'finish' TERM INT EXIT
while read -r notif_folder notif_type notif_file; do
if echo "$notif_type" | grep -qE "CREATE|MOVED_TO|ATTRIB"; then
if echo "$notif_type" | grep -qE "CLOSE_WRITE|MOVED_TO"; then
_handle_new_notif_file "$notif_folder$notif_file"
fi
_notifications_hook
@@ -87,9 +98,10 @@ monitor() {
}
_menu_lines() {
find "$SXMO_NOTIFDIR" -type f | while read -r file; do
msg="$(tail -n+3 "$file" | tr "\n^" " ")"
hrandmin="$(stat --printf %y "$file" | grep -oE '[0-9]{2}:[0-9]{2}')"
find "$SXMO_NOTIFDIR" -type f 2>/dev/null | while read -r file; do
[ -f "$file" ] || continue
msg="$(tail -n+3 "$file" 2>/dev/null | tr "\n^" " ")"
hrandmin="$(stat -c %y "$file" 2>/dev/null | grep -oE '[0-9]{2}:[0-9]{2}' | head -n1)"
cat <<EOF
$hrandmin $msg^$file
EOF
@@ -98,9 +110,9 @@ EOF
menu() {
choices="$(cat <<EOF
$icon_cls Close Menu
$icon_del Clear Notifications
$(_menu_lines)
$icon_cls Close Menu
EOF
)"
@@ -115,22 +127,27 @@ EOF
exit
;;
"$icon_del Clear Notifications")
rm "$SXMO_NOTIFDIR"/*
# we must update statusbar here because this function depends
# on number of files in $SXMO_NOTIFDIR
sxmo_hook_statusbar.sh notifications
clear_all
exit
;;
*)
file="$(
printf "%b" "$choices" |
grep -F "$picked" |
cut -d^ -f2
cut -d^ -f2 |
head -n1
)"
group="$(awk NR==1 "$file")"
action="$(awk NR==2 "$file")"
if [ -n "$file" ] && [ -f "$file" ]; then
group="$(awk NR==1 "$file" 2>/dev/null)"
action="$(awk NR==2 "$file" 2>/dev/null)"
_clear_notif_group "$group"
rm -f "$file" 2>/dev/null || true
dunstctl close-all >/dev/null 2>&1 || true
sxmo_hook_statusbar.sh notifications >/dev/null 2>&1 || true
if [ -n "$action" ] && [ "$action" != "none" ]; then
setsid -f sh -c "$action" > /dev/null 2>&1
fi
fi
;;
esac
}
@@ -167,7 +184,7 @@ new() {
action="$1"
msg="$2"
touch "$SXMO_NOTIFDIR/$id"
mkdir -p "$SXMO_NOTIFDIR"
printf "%s\n%s\n%b\n" "$group" "$action" "$msg" > "$SXMO_NOTIFDIR/$id"
sxmo_hook_statusbar.sh notifications
@@ -182,17 +199,20 @@ Subcommands:
Watch for new notification, dispatch libnotify events, handle actions
menu
Open a menu to dismiss notifications
clear
Clear all notifications and close active Dunst popups
new [-i id] [-g group] <action> <message...>
Register a new notification
EOF
}
cmd="$1"
shift
shift 2>/dev/null || true
case "$cmd" in
monitor) monitor "$@";;
menu) menu "$@";;
new) new "$@";;
monitor) monitor "$@" ;;
menu) menu "$@" ;;
clear) clear_all ;;
new) new "$@" ;;
*)
usage
exit 1
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -8,7 +8,7 @@ if [ -r "$CONF_FILE" ]; then
. "$CONF_FILE"
fi
SIM_WAIT_TIME="${SIM_WAIT_TIME:-${sim_wait_time:-4}}"
SIM_WAIT_TIME="${SIM_WAIT_TIME:-${sim_wait_time:-10}}"
PREFERRED_SIM_SLOT="${PREFERRED_SIM_SLOT:-${preferred_sim_slot:-1}}"
QMICLI_MODEM=""
@@ -16,15 +16,25 @@ detect_qmicli_modem() {
if ! command -v qmicli >/dev/null 2>&1; then
return 1
fi
if [ -e "/dev/modem" ]; then
QMICLI_MODEM="qmicli --silent -d /dev/modem"
return 0
elif [ -e "/dev/wwan0qmi0" ]; then
QMICLI_MODEM="qmicli --silent -d /dev/wwan0qmi0 --device-open-qmi"
return 0
elif qmicli --silent -pd qrtr://0 --uim-noop >/dev/null 2>&1; then
if qmicli --silent -pd qrtr://0 --uim-noop >/dev/null 2>&1; then
QMICLI_MODEM="qmicli --silent -pd qrtr://0"
return 0
elif [ -e "/dev/wwan0qmi0" ]; then
if qmicli --silent -pd /dev/wwan0qmi0 --device-open-qmi --uim-noop >/dev/null 2>&1; then
QMICLI_MODEM="qmicli --silent -pd /dev/wwan0qmi0 --device-open-qmi"
return 0
elif qmicli --silent -d /dev/wwan0qmi0 --device-open-qmi --uim-noop >/dev/null 2>&1; then
QMICLI_MODEM="qmicli --silent -d /dev/wwan0qmi0 --device-open-qmi"
return 0
fi
elif [ -e "/dev/modem" ]; then
if qmicli --silent -pd /dev/modem --uim-noop >/dev/null 2>&1; then
QMICLI_MODEM="qmicli --silent -pd /dev/modem"
return 0
elif qmicli --silent -d /dev/modem --uim-noop >/dev/null 2>&1; then
QMICLI_MODEM="qmicli --silent -d /dev/modem"
return 0
fi
fi
return 1
}
@@ -64,6 +74,8 @@ parse_uim_card_status() {
pin1_state[s] = "not-initialized"
pin1_ret[s] = 0
puk1_ret[s] = 0
want_aid[s] = 0
need_aid_line[s] = 0
}
}
/Primary GW:/ {
@@ -95,7 +107,7 @@ parse_uim_card_status() {
}
if (!in_app && $0 ~ /Card state:/) {
line = $0
sub(/.*Card state: \x27/, "", line)
sub(/.*Card state:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
rawstate[cur_slot] = line
if (line == "present") cstate[cur_slot] = "present"
@@ -104,25 +116,25 @@ parse_uim_card_status() {
}
if (!in_app && $0 ~ /UPIN state:/) {
line = $0
sub(/.*UPIN state: \x27/, "", line)
sub(/.*UPIN state:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
upin_state[cur_slot] = line
}
if (!in_app && $0 ~ /UPIN retries:/) {
line = $0
sub(/.*UPIN retries: \x27/, "", line)
sub(/.*UPIN retries:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
upin_ret[cur_slot] = line + 0
}
if (!in_app && $0 ~ /UPUK retries:/) {
line = $0
sub(/.*UPUK retries: \x27/, "", line)
sub(/.*UPUK retries:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
upuk_ret[cur_slot] = line + 0
}
if (in_app && $0 ~ /Application type:/) {
line = $0
sub(/.*Application type: \x27/, "", line)
sub(/.*Application type:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
if (line ~ /^usim/) {
app_type[cur_slot] = "usim"
@@ -130,13 +142,16 @@ parse_uim_card_status() {
} else if (line ~ /^sim/ && app_type[cur_slot] != "usim") {
app_type[cur_slot] = "sim"
want_aid[cur_slot] = 1
} else if (app_type[cur_slot] == "none") {
app_type[cur_slot] = "sim"
want_aid[cur_slot] = 1
} else {
want_aid[cur_slot] = 0
}
}
if (in_app && $0 ~ /Application state:/ && want_aid[cur_slot] > 0) {
line = $0
sub(/.*Application state: \x27/, "", line)
sub(/.*Application state:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
app_state[cur_slot] = line
}
@@ -147,26 +162,26 @@ parse_uim_card_status() {
if (in_app && need_aid_line[cur_slot] == 1) {
line = $0
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
if (line != "") {
if (line != "" && line !~ /:.*\x27/) {
aid[cur_slot] = line
}
need_aid_line[cur_slot] = 0
}
if (in_app && $0 ~ /PIN1 state:/ && want_aid[cur_slot] > 0) {
line = $0
sub(/.*PIN1 state: \x27/, "", line)
sub(/.*PIN1 state:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
pin1_state[cur_slot] = line
}
if (in_app && $0 ~ /PIN1 retries:/ && want_aid[cur_slot] > 0) {
line = $0
sub(/.*PIN1 retries: \x27/, "", line)
sub(/.*PIN1 retries:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
pin1_ret[cur_slot] = line + 0
}
if (in_app && $0 ~ /PUK1 retries:/ && want_aid[cur_slot] > 0) {
line = $0
sub(/.*PUK1 retries: \x27/, "", line)
sub(/.*PUK1 retries:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
puk1_ret[cur_slot] = line + 0
}
@@ -192,8 +207,116 @@ parse_uim_card_status() {
'
}
parse_nas_serving_system() {
raw_nas="$1"
printf "%s\n" "$raw_nas" | awk '
BEGIN {
reg_state = "unknown"
cs_state = "unknown"
ps_state = "unknown"
rat = ""
mcc = ""
mnc = ""
op_desc = ""
roam = "off"
in_rat = 0
in_plmn = 0
in_roam = 0
}
/^[[:space:]]*Registration state:/ {
line = $0
sub(/.*Registration state:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
reg_state = line
in_rat = 0; in_plmn = 0; in_roam = 0
next
}
/^[[:space:]]*CS:/ {
line = $0
sub(/.*CS:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
cs_state = line
next
}
/^[[:space:]]*PS:/ {
line = $0
sub(/.*PS:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
ps_state = line
next
}
/^[[:space:]]*Radio interfaces:/ {
in_rat = 1; in_plmn = 0; in_roam = 0
next
}
/^[[:space:]]*Data service capabilities:/ {
in_rat = 0
next
}
/^[[:space:]]*Current PLMN:/ {
in_plmn = 1; in_rat = 0; in_roam = 0
next
}
/^[[:space:]]*Roaming indicators:/ {
in_roam = 1; in_plmn = 0; in_rat = 0
next
}
/^[[:space:]]*Default roaming status:/ {
line = $0
sub(/.*Default roaming status:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
roam = line
in_roam = 0
next
}
in_rat && /^[[:space:]]*\[[0-9]+\]:/ {
line = $0
sub(/.*\[[0-9]+\]:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
if (line != "" && line != "none" && rat == "") rat = line
}
in_plmn && /MCC:/ {
line = $0
sub(/.*MCC:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
mcc = line
}
in_plmn && /MNC:/ {
line = $0
sub(/.*MNC:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
mnc = line
}
in_plmn && /Description:/ {
line = $0
sub(/.*Description:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
gsub(/[\r\n\t]/, " ", line)
op_desc = line
in_plmn = 0
}
in_roam && /^[[:space:]]*\[[0-9]+\]:/ {
line = $0
sub(/.*\[[0-9]+\]:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
if (line == "on") roam = "on"
}
END {
printf "NAS_REG_STATE=%s\n", reg_state
printf "NAS_CS_STATE=%s\n", cs_state
printf "NAS_PS_STATE=%s\n", ps_state
printf "NAS_RAT=%s\n", rat
printf "NAS_MCC=%s\n", mcc
printf "NAS_MNC=%s\n", mnc
printf "NAS_OPERATOR=%s\n", op_desc
printf "NAS_ROAMING=%s\n", roam
}
'
}
save_preferred_slot() {
target_slot="$1"
PREFERRED_SIM_SLOT="$target_slot"
if [ -f "$CONF_FILE" ]; then
if grep -q "^preferred_sim_slot=" "$CONF_FILE"; then
sed -i "s/^preferred_sim_slot=.*/preferred_sim_slot=$target_slot/" "$CONF_FILE"
@@ -201,7 +324,7 @@ save_preferred_slot() {
printf "\npreferred_sim_slot=%s\n" "$target_slot" >> "$CONF_FILE"
fi
else
printf "sim_wait_time=4\npreferred_sim_slot=%s\n" "$target_slot" > "$CONF_FILE"
printf "sim_wait_time=10\npreferred_sim_slot=%s\n" "$target_slot" > "$CONF_FILE"
fi
}
@@ -214,52 +337,154 @@ provision_slot() {
return 2
fi
$QMICLI_MODEM --dms-set-operating-mode=online >/dev/null 2>&1 || true
QMI_CARDS="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)"
count=0
while ! printf "%s" "$QMI_CARDS" | grep -Fq "Card state: 'present'"; do
while : ; do
if [ -n "$QMI_CARDS" ]; then
eval "$(parse_uim_card_status "$QMI_CARDS" "$req_slot")"
if [ "$req_slot" = "2" ] && [ "$SLOT2_STATE" = "present" ] && [ "$SLOT2_APP_TYPE" != "none" ]; then
break
elif [ "$req_slot" != "2" ] && [ "$SLOT1_STATE" = "present" ] && [ "$SLOT1_APP_TYPE" != "none" ]; then
break
elif [ "$count" -ge 3 ] && { { [ "$SLOT1_STATE" = "present" ] && [ "$SLOT1_APP_TYPE" != "none" ]; } || { [ "$SLOT2_STATE" = "present" ] && [ "$SLOT2_APP_TYPE" != "none" ]; }; }; then
break
fi
fi
if [ "$count" -ge "$SIM_WAIT_TIME" ]; then
echo "No SIM detected after $SIM_WAIT_TIME seconds."
return 0
break
fi
sleep 1
count=$((count + 1))
QMI_CARDS="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)"
done
if [ -z "$QMI_CARDS" ]; then
echo "No SIM card status returned."
return 0
fi
eval "$(parse_uim_card_status "$QMI_CARDS" "$req_slot")"
chosen_slot=""
chosen_aid=""
if [ "$req_slot" = "2" ]; then
if [ "$SLOT2_STATE" = "present" ] && [ -n "$SLOT2_AID" ]; then
if [ "$SLOT2_STATE" = "present" ]; then
chosen_slot="2"
chosen_aid="$SLOT2_AID"
elif [ "$SLOT1_STATE" = "present" ] && [ -n "$SLOT1_AID" ]; then
elif [ "$SLOT1_STATE" = "present" ]; then
chosen_slot="1"
chosen_aid="$SLOT1_AID"
fi
else
if [ "$SLOT1_STATE" = "present" ] && [ -n "$SLOT1_AID" ]; then
if [ "$SLOT1_STATE" = "present" ]; then
chosen_slot="1"
chosen_aid="$SLOT1_AID"
elif [ "$SLOT2_STATE" = "present" ] && [ -n "$SLOT2_AID" ]; then
elif [ "$SLOT2_STATE" = "present" ]; then
chosen_slot="2"
chosen_aid="$SLOT2_AID"
fi
fi
if [ -z "$chosen_slot" ] || [ -z "$chosen_aid" ]; then
echo "No valid USIM/SIM application found on any slot."
if [ -z "$chosen_slot" ]; then
echo "No SIM detected after $SIM_WAIT_TIME seconds."
return 0
fi
if [ "$PRIMARY_GW_SLOT" != "0" ]; then
if [ "$chosen_slot" != "$PREFERRED_SIM_SLOT" ]; then
save_preferred_slot "$chosen_slot"
fi
# If already provisioned on the chosen slot and application is ready, keep session intact
eval "chosen_app_state=\$SLOT${chosen_slot}_APP_STATE"
if [ "$PRIMARY_GW_SLOT" = "$chosen_slot" ] && [ "$chosen_app_state" = "ready" ]; then
echo "Slot $chosen_slot already active and ready."
return 0
fi
if [ "$PRIMARY_GW_SLOT" != "0" ] && [ "$PRIMARY_GW_SLOT" != "$chosen_slot" ]; then
$QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=primary-gw-provisioning' >/dev/null 2>&1 || true
fi
if [ -n "$chosen_aid" ]; then
echo "Selecting AID $chosen_aid on slot $chosen_slot"
$QMICLI_MODEM --uim-change-provisioning-session="slot=$chosen_slot,activate=yes,session-type=primary-gw-provisioning,aid=$chosen_aid" >/dev/null
return $?
if ! $QMICLI_MODEM --uim-change-provisioning-session="slot=$chosen_slot,activate=yes,session-type=primary-gw-provisioning,aid=$chosen_aid" >/dev/null 2>&1; then
$QMICLI_MODEM --uim-change-provisioning-session="slot=$chosen_slot,activate=yes,session-type=primary-gw-provisioning" >/dev/null 2>&1 || true
fi
else
echo "Selecting slot $chosen_slot"
$QMICLI_MODEM --uim-change-provisioning-session="slot=$chosen_slot,activate=yes,session-type=primary-gw-provisioning" >/dev/null 2>&1 || true
fi
return 0
}
enable_and_register_modem() {
target_slot="${1:-$PREFERRED_SIM_SLOT}"
if detect_qmicli_modem; then
$QMICLI_MODEM --dms-set-operating-mode=online >/dev/null 2>&1 || true
$QMICLI_MODEM --nas-initiate-network-register >/dev/null 2>&1 || true
fi
i=0
mm_state=""
while [ "$i" -lt 12 ]; do
mm_state="$(mmcli -m any -K 2>/dev/null | awk -F': ' '/^modem\.generic\.state / {print $2; exit}' || true)"
if [ -n "$mm_state" ]; then
break
fi
sleep 1
i=$((i + 1))
done
if [ "$mm_state" = "failed" ]; then
if command -v rc-service >/dev/null 2>&1; then
rc-service modemmanager restart >/dev/null 2>&1 || true
elif command -v systemctl >/dev/null 2>&1; then
systemctl restart ModemManager >/dev/null 2>&1 || true
fi
i=0
mm_state=""
while [ "$i" -lt 12 ]; do
mm_state="$(mmcli -m any -K 2>/dev/null | awk -F': ' '/^modem\.generic\.state / {print $2; exit}' || true)"
if [ -n "$mm_state" ] && [ "$mm_state" != "failed" ]; then
break
fi
sleep 1
i=$((i + 1))
done
fi
if [ -z "$mm_state" ] || [ "$mm_state" = "locked" ]; then
return 0
fi
i=0
while [ "$i" -lt 10 ]; do
mm_state="$(mmcli -m any -K 2>/dev/null | awk -F': ' '/^modem\.generic\.state / {print $2; exit}' || true)"
case "$mm_state" in
disabled)
mmcli -m any -e >/dev/null 2>&1 || true
;;
enabled)
mmcli -m any --3gpp-register-home >/dev/null 2>&1 || true
break
;;
searching|registered|connected|connecting)
break
;;
locked)
break
;;
esac
sleep 1
i=$((i + 1))
done
mmcli -m any --3gpp-register-home >/dev/null 2>&1 || true
if command -v sxmo_hook_statusbar.sh >/dev/null 2>&1; then
DISPLAY="${DISPLAY:-:0}" sxmo_hook_statusbar.sh modem >/dev/null 2>&1 || true
fi
}
cmd="${1:-boot}"
@@ -276,7 +501,8 @@ case "$cmd" in
;;
esac
provision_slot "$PREFERRED_SIM_SLOT" 45
exit $?
( enable_and_register_modem "$PREFERRED_SIM_SLOT" ) >/dev/null 2>&1 </dev/null &
exit 0
;;
status)
@@ -293,6 +519,10 @@ case "$cmd" in
fi
echo "MODEM_AVAILABLE=1"
parse_uim_card_status "$QMI_CARDS" "$PREFERRED_SIM_SLOT"
QMI_NAS="$($QMICLI_MODEM --nas-get-serving-system 2>/dev/null || true)"
if [ -n "$QMI_NAS" ]; then
parse_nas_serving_system "$QMI_NAS"
fi
exit 0
;;
@@ -303,13 +533,14 @@ case "$cmd" in
exit 1
fi
save_preferred_slot "$target"
SIM_WAIT_TIME=2
SIM_WAIT_TIME=3
provision_slot "$target" 5
if command -v rc-service >/dev/null 2>&1; then
rc-service modemmanager restart >/dev/null 2>&1 || true
elif command -v systemctl >/dev/null 2>&1; then
systemctl restart ModemManager >/dev/null 2>&1 || true
fi
( enable_and_register_modem "$target" ) >/dev/null 2>&1 </dev/null &
exit 0
;;
@@ -320,18 +551,27 @@ case "$cmd" in
echo "Missing PIN" >&2
exit 1
fi
verified=0
if detect_qmicli_modem; then
if $QMICLI_MODEM --uim-verify-pin="PIN1,$pin,session-type=card-slot-$slot" >/dev/null 2>&1; then
verified=1
elif $QMICLI_MODEM --uim-verify-pin="PIN1,$pin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then
verified=1
fi
fi
if [ "$verified" = "0" ]; then
sim_id="$(mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true)"
if [ -n "$sim_id" ] && mmcli -i "$sim_id" --pin="$pin" >/dev/null 2>&1; then
exit 0
verified=1
fi
if detect_qmicli_modem; then
if $QMICLI_MODEM --uim-verify-pin="PIN1,$pin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then
exit 0
fi
if $QMICLI_MODEM --uim-verify-pin="PIN1,$pin,session-type=card-slot-$slot" >/dev/null 2>&1; then
if [ "$verified" = "1" ]; then
save_preferred_slot "$slot"
SIM_WAIT_TIME=2
provision_slot "$slot" 3 >/dev/null 2>&1 || true
( enable_and_register_modem "$slot" ) >/dev/null 2>&1 </dev/null &
exit 0
fi
fi
exit 1
;;
@@ -343,18 +583,27 @@ case "$cmd" in
echo "Missing PUK or new PIN" >&2
exit 1
fi
unblocked=0
if detect_qmicli_modem; then
if $QMICLI_MODEM --uim-unblock-pin="PIN1,$puk,$newpin,session-type=card-slot-$slot" >/dev/null 2>&1; then
unblocked=1
elif $QMICLI_MODEM --uim-unblock-pin="PIN1,$puk,$newpin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then
unblocked=1
fi
fi
if [ "$unblocked" = "0" ]; then
sim_id="$(mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true)"
if [ -n "$sim_id" ] && mmcli -i "$sim_id" --puk="$puk" --pin="$newpin" >/dev/null 2>&1; then
exit 0
unblocked=1
fi
if detect_qmicli_modem; then
if $QMICLI_MODEM --uim-unblock-pin="PIN1,$puk,$newpin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then
exit 0
fi
if $QMICLI_MODEM --uim-unblock-pin="PIN1,$puk,$newpin,session-type=card-slot-$slot" >/dev/null 2>&1; then
if [ "$unblocked" = "1" ]; then
save_preferred_slot "$slot"
SIM_WAIT_TIME=2
provision_slot "$slot" 3 >/dev/null 2>&1 || true
( enable_and_register_modem "$slot" ) >/dev/null 2>&1 </dev/null &
exit 0
fi
fi
exit 1
;;
@@ -366,6 +615,14 @@ case "$cmd" in
echo "Usage: $0 set-pin-lock <1|2> <enable|disable> <pin>" >&2
exit 1
fi
if detect_qmicli_modem; then
if $QMICLI_MODEM --uim-set-pin-protection="PIN1,$mode,$pin,session-type=card-slot-$slot" >/dev/null 2>&1; then
exit 0
fi
if $QMICLI_MODEM --uim-set-pin-protection="PIN1,$mode,$pin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then
exit 0
fi
fi
sim_id="$(mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true)"
if [ -n "$sim_id" ]; then
if [ "$mode" = "enable" ] && mmcli -i "$sim_id" --enable-pin --pin="$pin" >/dev/null 2>&1; then
@@ -374,14 +631,6 @@ case "$cmd" in
exit 0
fi
fi
if detect_qmicli_modem; then
if $QMICLI_MODEM --uim-set-pin-protection="PIN1,$mode,$pin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then
exit 0
fi
if $QMICLI_MODEM --uim-set-pin-protection="PIN1,$mode,$pin,session-type=card-slot-$slot" >/dev/null 2>&1; then
exit 0
fi
fi
exit 1
;;
@@ -393,17 +642,17 @@ case "$cmd" in
echo "Usage: $0 change-pin <1|2> <oldpin> <newpin>" >&2
exit 1
fi
sim_id="$(mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true)"
if [ -n "$sim_id" ] && mmcli -i "$sim_id" --change-pin="$newpin" --pin="$oldpin" >/dev/null 2>&1; then
exit 0
fi
if detect_qmicli_modem; then
if $QMICLI_MODEM --uim-change-pin="PIN1,$oldpin,$newpin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then
exit 0
fi
if $QMICLI_MODEM --uim-change-pin="PIN1,$oldpin,$newpin,session-type=card-slot-$slot" >/dev/null 2>&1; then
exit 0
fi
if $QMICLI_MODEM --uim-change-pin="PIN1,$oldpin,$newpin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then
exit 0
fi
fi
sim_id="$(mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true)"
if [ -n "$sim_id" ] && mmcli -i "$sim_id" --change-pin="$newpin" --pin="$oldpin" >/dev/null 2>&1; then
exit 0
fi
exit 1
;;
@@ -505,7 +754,12 @@ case "$cmd" in
exit 1
fi
resp=""
for candidate in "$port" /dev/wwan0at1 /dev/wwan0at0; do
if [ "$PREFERRED_SIM_SLOT" = "2" ]; then
at_candidates="$port /dev/wwan0at1 /dev/wwan0at0"
else
at_candidates="$port /dev/wwan0at0 /dev/wwan0at1"
fi
for candidate in $at_candidates; do
case "$candidate" in
wwan0at0|wwan0at1)
dev="/dev/$candidate"
@@ -518,7 +772,7 @@ case "$cmd" in
;;
esac
if [ -n "$dev" ] && [ -c "$dev" ]; then
resp="$(timeout 2 sh -c '
resp="$(timeout 3 sh -c '
dev="$1"
cmd="$2"
tmpf="/tmp/at_resp_$$.txt"
@@ -528,7 +782,7 @@ case "$cmd" in
sleep 0.04
printf "%s\r\n" "$cmd" > "$dev" 2>/dev/null || true
i=0
while [ "$i" -lt 12 ]; do
while [ "$i" -lt 16 ]; do
if grep -qE "(^|[[:space:]])(OK|ERROR|\+CME ERROR|\+CMS ERROR)([[:space:]]|$)" "$tmpf" 2>/dev/null; then
break
fi
@@ -558,6 +812,84 @@ case "$cmd" in
exit 1
;;
ussd)
ussd_mode="${2:-initiate}"
shift 2 2>/dev/null || true
ussd_code="$*"
if [ "$ussd_mode" = "cancel" ]; then
mmcli -m any --3gpp-ussd-cancel >/dev/null 2>&1 || true
"$0" at auto 'AT+CUSD=2' >/dev/null 2>&1 || true
echo "USSD session cancelled."
exit 0
fi
if [ -z "$ussd_code" ]; then
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}')"
if [ "$mm_state" = "disabled" ] || [ -z "$mm_state" ]; then
mmcli -m any -e >/dev/null 2>&1 || true
sleep 1
fi
if [ "$ussd_mode" = "respond" ]; then
mm_out="$(mmcli -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=$?
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
fi
# Fallback to direct AT+CUSD on the active SIM AT port
if [ "$PREFERRED_SIM_SLOT" = "2" ]; then
at_candidates="/dev/wwan0at1 /dev/wwan0at0"
else
at_candidates="/dev/wwan0at0 /dev/wwan0at1"
fi
for dev in $at_candidates; do
[ -c "$dev" ] || continue
cusd_out="$(timeout 12 sh -c '
dev="$1"
code="$2"
tmpf="/tmp/ussd_resp_$$.txt"
rm -f "$tmpf"
cat "$dev" > "$tmpf" 2>/dev/null &
rpid=$!
sleep 0.05
printf "AT+CSCS=\"GSM\";+CUSD=1,\"%s\",15\r\n" "$code" > "$dev" 2>/dev/null || true
i=0
while [ "$i" -lt 80 ]; do
if grep -qE "\+CUSD:[[:space:]]*[0-9]+" "$tmpf" 2>/dev/null; then
break
fi
if grep -qE "(^|[[:space:]])(\+CME ERROR|\+CMS ERROR|ERROR)([[:space:]]|$)" "$tmpf" 2>/dev/null; then
break
fi
sleep 0.1
i=$((i + 1))
done
kill "$rpid" 2>/dev/null || true
wait "$rpid" 2>/dev/null || true
tr -d "\r" < "$tmpf" 2>/dev/null || true
rm -f "$tmpf"
' _ "$dev" "$ussd_code" 2>/dev/null || true)"
cusd_msg="$(printf "%s\n" "$cusd_out" | sed -n 's/.*+CUSD:[[:space:]]*[0-9]*,[[:space:]]*"\([^"]*\)".*/\1/p' | tail -n1)"
if [ -n "$cusd_msg" ]; then
printf "%s\n" "$cusd_msg"
exit 0
fi
done
if [ -n "$mm_out" ]; then
printf "%s\n" "$mm_out"
else
echo "ERROR: USSD request failed"
fi
exit 1
;;
sim-contacts-import)
req_slot="${2:-auto}"
if ! wait_for_qmicli_modem 3; then
@@ -688,8 +1020,8 @@ case "$cmd" in
# Method 2: Direct QMI UIM EF_ADN (3F00:7F10:6F3A) record fallback
if [ ! -s "$tmp_contacts" ]; then
adn_attr="$($QMICLI_MODEM --uim-get-file-attributes=0x3F00,0x7F10,0x6F3A 2>/dev/null || true)"
reclen="$(printf "%s\n" "$adn_attr" | awk -F"'" '/Record length:/ {print $2 + 0; exit}')"
reccount="$(printf "%s\n" "$adn_attr" | awk -F"'" '/Record count:/ {print $2 + 0; exit}')"
reclen="$(printf "%s\n" "$adn_attr" | awk '/Record (size|length):/ {gsub(/[^0-9]/, "", $NF); print $NF + 0; exit}')"
reccount="$(printf "%s\n" "$adn_attr" | awk '/Record count:/ {gsub(/[^0-9]/, "", $NF); print $NF + 0; exit}')"
if [ -n "$reclen" ] && [ -n "$reccount" ] && [ "$reclen" -ge 14 ] 2>/dev/null && [ "$reccount" -ge 1 ] 2>/dev/null; then
if [ "$reccount" -gt 250 ]; then
reccount=250
@@ -8,7 +8,7 @@
horizontal_padding = 6
frame_width = 2
frame_color = "#005577"
idle_threshold = 120
idle_threshold = 0
font = Fira Code 9.5
markup = full
max_icon_size = 24
@@ -22,16 +22,16 @@
[urgency_low]
background = "#222222"
foreground = "#cccccc"
timeout = 8
timeout = 5
[urgency_normal]
background = "#1c1c1c"
foreground = "#ffffff"
frame_color = "#005577"
timeout = 12
timeout = 8
[urgency_critical]
background = "#900000"
foreground = "#ffffff"
frame_color = "#ff0000"
timeout = 0
timeout = 10
@@ -15,22 +15,68 @@ newstate="$2"
sxmo_debug "$2 (old: $1 [reason: $3])"
case "$newstate" in
"locked")
sxmo_log "State is locked. Opening SIM PIN/PUK unlock dialog."
open_sim_unlock_ui() {
(
i=0
while [ "$i" -lt 40 ] && [ ! -p /tmp/dwm_plugin_modem.fifo ]; do
sleep 0.2
i=$((i + 1))
done
if command -v xmodem >/dev/null 2>&1; then
xmodem unlock >/dev/null 2>&1 &
DISPLAY="${DISPLAY:-:0}" xmodem unlock >/dev/null 2>&1 &
elif ! pgrep -f "sxmo_unlocksim.sh" >/dev/null; then
sxmo_unlocksim.sh &
fi
) &
}
case "$newstate" in
"locked")
sxmo_log "State is locked. Opening SIM PIN/PUK unlock dialog."
open_sim_unlock_ui
;;
"failed")
sxmo_log "State is failed. Checking QMI UIM status for SIM2/PIN lock."
(
uim_st="$(doas -n /usr/libexec/msm-modem-uim-selection status 2>/dev/null || /usr/libexec/msm-modem-uim-selection status 2>/dev/null || true)"
if printf "%s\n" "$uim_st" | grep -qE "SLOT[12]_PIN1_STATE=(enabled-not-verified|blocked)|SLOT[12]_APP_STATE=(pin1|puk1)"; then
sxmo_log "QMI UIM reports locked SIM while ModemManager state is failed; opening unlock UI."
open_sim_unlock_ui
elif printf "%s\n" "$uim_st" | grep -qE "SLOT[12]_STATE=present" && printf "%s\n" "$uim_st" | grep -q "^PRIMARY_GW_SLOT=0$"; then
now_ts="$(date +%s 2>/dev/null || echo 0)"
last_ts="$(cat /tmp/sxmo_modem_reprov.stamp 2>/dev/null || echo 0)"
if [ $((now_ts - last_ts)) -ge 25 ]; then
echo "$now_ts" > /tmp/sxmo_modem_reprov.stamp
sxmo_log "Unprovisioned SIM detected in failed state; running msm-modem-uim-selection boot."
doas -n /usr/libexec/msm-modem-uim-selection boot >/dev/null 2>&1 || true
sxmo_hook_statusbar.sh modem >/dev/null 2>&1 || true
fi
fi
) &
;;
"disabled")
sxmo_log "State is disabled. Enabling modem and registering on network."
(
mmcli -m any -e >/dev/null 2>&1 || true
sleep 1
mmcli -m any --3gpp-register-home >/dev/null 2>&1 || true
sxmo_hook_statusbar.sh modem >/dev/null 2>&1 || true
) &
;;
"enabling")
sxmo_log "State is enabling. Clearing stale call files."
rm "$XDG_RUNTIME_DIR"/sxmo_calls/* 2>/dev/null || true
rm -f "$SXMO_NOTIFDIR"/incomingcall* 2>/dev/null || true
;;
"registered")
sxmo_log "State is registered. Checking for calls and messages."
"enabled")
sxmo_log "State is enabled. Requesting 3GPP network registration."
(
mmcli -m any --3gpp-register-home >/dev/null 2>&1 || true
sxmo_hook_statusbar.sh modem >/dev/null 2>&1 || true
) &
;;
"registered"|"connected")
sxmo_log "State is $newstate. Checking for calls and messages."
# kill the pin-entry dmenu if still open
pkill -f sxmo_unlocksim.sh 2>/dev/null || true
@@ -59,6 +59,13 @@ set_modem() {
MODEMSTATE="$(printf %s "$MMCLI" | jq -r .modem.generic.state)"
fi
if [ "$MODEMSTATE" = "failed" ] || [ "$MODEMSTATE" = "nomodem" ]; then
uim_st="$(doas -n /usr/libexec/msm-modem-uim-selection status 2>/dev/null || /usr/libexec/msm-modem-uim-selection status 2>/dev/null || true)"
if printf "%s\n" "$uim_st" | grep -qE "SLOT[12]_PIN1_STATE=(enabled-not-verified|blocked)|SLOT[12]_APP_STATE=(pin1|puk1)"; then
MODEMSTATE="locked"
fi
fi
case "$MODEMSTATE" in
nomodem)
fgcolor=red
@@ -119,7 +126,7 @@ set_modem() {
sxmobar -a -f "$fgcolor" -b "$bgcolor" -t "$style" \
modem-state 10 "$MODEMSTATECMP"
if [ "$MODEMSTATE" = nomodem ]; then
if [ "$MODEMSTATE" = nomodem ] || [ "$MODEMSTATE" = locked ] || [ "$MODEMSTATE" = failed ] || [ "$MODEMSTATE" = unknown ]; then
sxmobar -d modem-tech
sxmobar -d modem-signal
return
@@ -131,7 +138,7 @@ set_modem() {
style=normal
# see https://www.freedesktop.org/software/ModemManager/api/latest/ModemManager-Flags-and-Enumerations.html#MMModemAccessTechnology
USEDTECHS="$(printf %s "$MMCLI" | jq -r '.modem.generic."access-technologies"[]')"
USEDTECHS="$(printf %s "$MMCLI" | jq -r '.modem.generic."access-technologies"[]? // empty' 2>/dev/null)"
case "$USEDTECHS" in
*5gnr*)
MODEMTECHCMP="$icon_modem_fiveg"
@@ -178,7 +185,10 @@ set_modem() {
case "$MODEMSTATE" in
registered|connected|connecting|disconnecting)
MODEMSIGNAL="$(printf %s "$MMCLI" | jq -r '.modem.generic."signal-quality".value')"
MODEMSIGNAL="$(printf %s "$MMCLI" | jq -r '.modem.generic."signal-quality".value // 0' 2>/dev/null)"
case "$MODEMSIGNAL" in
''|*[!0-9]*) MODEMSIGNAL=0 ;;
esac
if [ "$MODEMSIGNAL" -lt 33 ]; then
MODEMSIGNALCMP="$icon_modem_signal_1"
elif [ "$MODEMSIGNAL" -lt 66 ]; then
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+187 -23
View File
@@ -62,12 +62,15 @@ struct SimSlotInfo {
bool needsPin() const {
return state == "present" &&
(pin1State == "enabled-not-verified" || appState == "pin1");
(pin1State == "enabled-not-verified" ||
appState.rfind("pin1", 0) == 0);
}
bool needsPuk() const {
return state == "present" &&
(pin1State == "blocked" || appState == "puk1");
(pin1State == "blocked" ||
appState.rfind("puk1", 0) == 0 ||
appState == "pin1-blocked");
}
bool isPinEnabled() const {
@@ -88,6 +91,13 @@ struct ModemStatusData {
std::string mmFailedReason;
std::string mmPowerState = "off";
std::string mmUnlockRequired;
std::string mmRegState;
std::string nasRegState;
std::string nasOperator;
std::string nasMcc;
std::string nasMnc;
std::string nasRoaming;
std::string nasRadio;
int mmPinRetries = -1;
int mmPukRetries = -1;
std::string operatorName;
@@ -103,6 +113,56 @@ struct ModemStatusData {
std::string simOperatorName;
std::string currentModes;
int modePresetIdx = 0;
bool isNetworkRegistered() const {
return mmRegState == "home" || mmRegState == "roaming" ||
nasRegState == "registered" ||
mmState == "registered" || mmState == "connected";
}
bool isNetworkSearching() const {
return mmRegState == "searching" || nasRegState == "searching" ||
mmState == "searching" || mmState == "enabling" ||
mmState == "connecting";
}
std::string regStatusLabel() const {
if (slots[0].needsPuk() || slots[1].needsPuk() ||
mmUnlockRequired == "sim-puk") {
return "SIM PUK Required";
}
if (slots[0].needsPin() || slots[1].needsPin() ||
mmUnlockRequired == "sim-pin" || mmState == "locked") {
return "SIM PIN Required";
}
if (mmRegState == "home" ||
(nasRegState == "registered" && nasRoaming != "on")) {
return "Registered (Home)";
}
if (mmRegState == "roaming" ||
(nasRegState == "registered" && nasRoaming == "on")) {
return "Registered (Roaming)";
}
if (mmState == "connected" || mmState == "registered") {
return "Registered";
}
if (isNetworkSearching()) {
return "Searching...";
}
if (mmRegState == "denied" || nasRegState == "registration-denied") {
return "Registration Denied";
}
if (slots[0].state != "present" && slots[1].state != "present") {
return "No SIM Inserted";
}
if (primaryGwSlot == 0) {
return "SIM Not Activated";
}
if (mmState == "disabled") {
return "Modem Disabled";
}
return "Not Registered";
}
};
struct RadioModePreset {
@@ -201,6 +261,12 @@ ModemStatusData queryModemAndSimStatus() {
if (!uimKv["PRIMARY_GW_SLOT"].empty()) {
d.primaryGwSlot = std::atoi(uimKv["PRIMARY_GW_SLOT"].c_str());
}
d.nasRegState = uimKv["NAS_REG_STATE"];
d.nasOperator = uimKv["NAS_OPERATOR"];
d.nasMcc = uimKv["NAS_MCC"];
d.nasMnc = uimKv["NAS_MNC"];
d.nasRoaming = uimKv["NAS_ROAMING"];
d.nasRadio = uimKv["NAS_RADIO"];
for (int i = 0; i < 2; ++i) {
std::string pfx = "SLOT" + std::to_string(i + 1) + "_";
@@ -244,6 +310,7 @@ ModemStatusData queryModemAndSimStatus() {
d.mmFailedReason = cleanMmVal(mmKv["modem.generic.state-failed-reason"]);
d.mmPowerState = cleanMmVal(mmKv["modem.generic.power-state"]);
d.mmUnlockRequired = cleanMmVal(mmKv["modem.generic.unlock-required"]);
d.mmRegState = cleanMmVal(mmKv["modem.3gpp.registration-state"]);
d.operatorName = cleanMmVal(mmKv["modem.3gpp.operator-name"]);
d.operatorCode = cleanMmVal(mmKv["modem.3gpp.operator-code"]);
d.accessTech = cleanMmVal(mmKv["modem.generic.access-technologies.value[1]"]);
@@ -309,6 +376,16 @@ ModemStatusData queryModemAndSimStatus() {
}
}
if (d.operatorName.empty() && !d.nasOperator.empty()) {
d.operatorName = d.nasOperator;
}
if (d.operatorCode.empty() && !d.nasMcc.empty() && !d.nasMnc.empty()) {
d.operatorCode = d.nasMcc + d.nasMnc;
}
if (d.accessTech.empty() && !d.nasRadio.empty() && d.nasRadio != "none") {
d.accessTech = d.nasRadio;
}
// Sync ModemManager lock info into active slot if QMI didn't report retries
int actIdx = (d.primaryGwSlot >= 1 && d.primaryGwSlot <= 2)
? (d.primaryGwSlot - 1)
@@ -395,8 +472,8 @@ int runModemApp(int argc, char** argv) {
return 0;
}
writeIntFile(daemonPidPath, static_cast<int>(::getpid()));
int fifoFd = setupPluginDaemonFifo("modem");
writeIntFile(daemonPidPath, static_cast<int>(::getpid()));
struct sigaction sa {};
sa.sa_handler = handleSignal;
@@ -702,10 +779,24 @@ int runModemApp(int argc, char** argv) {
line2 = "LOCKED (Enter PIN)";
line2Col = &colOrange;
} else if (s.isActiveProvisioned) {
std::string op = status.operatorName.empty() ? "Ready" : status.operatorName;
if (!status.ownNumber.empty()) op += " " + status.ownNumber;
line2 = op;
std::string reg = status.regStatusLabel();
if (!status.operatorName.empty()) {
reg += " - " + status.operatorName;
}
if (!status.accessTech.empty()) {
reg += " [" + status.accessTech + "]";
}
line2 = reg;
if (status.isNetworkRegistered()) {
line2Col = &colGreen;
} else if (status.isNetworkSearching()) {
line2Col = &colOrange;
} else {
line2Col = &colRed;
}
} else if (status.primaryGwSlot == 0) {
line2 = "Present (Unactivated - Press Select)";
line2Col = &colOrange;
} else {
line2 = "Present (Standby " + s.appType + ")";
line2Col = &colBlue;
@@ -726,6 +817,9 @@ int runModemApp(int argc, char** argv) {
else if (pState == "blocked") pState = "PUK Req";
line3 = pState + " (PIN:" + std::to_string(s.pin1Retries) +
" PUK:" + std::to_string(s.puk1Retries) + ")";
if (s.isActiveProvisioned && !status.ownNumber.empty()) {
line3 += " " + status.ownNumber;
}
} else {
line3 = "Insert nano-SIM & restart/switch";
}
@@ -736,8 +830,18 @@ int runModemApp(int argc, char** argv) {
// Quick Action Rows below the 2 SIM cards (4 rows, 24px each)
int actSlot = (simRowSel == 1) ? 2 : 1;
int otherSlot = (status.preferredSlot == 1) ? 2 : 1;
if (status.primaryGwSlot == 0) {
if (status.slots[1].state == "present" && status.slots[0].state != "present") {
otherSlot = 2;
} else if (status.slots[0].state == "present" && status.slots[1].state != "present") {
otherSlot = 1;
}
}
std::string quickRows[4] = {
std::string(kIconRefresh) + " Switch Active to SIM " + std::to_string(otherSlot),
std::string(kIconRefresh) +
(status.primaryGwSlot == 0
? (" Activate SIM " + std::to_string(otherSlot))
: (" Switch Active to SIM " + std::to_string(otherSlot))),
std::string(kIconUnlock) + " Unlock SIM (PIN / PUK)",
std::string(kIconLock) + " Enable / Disable SIM PIN",
std::string(kIconKey) + " Change SIM PIN Code",
@@ -762,29 +866,31 @@ int runModemApp(int argc, char** argv) {
XSetForeground(dpy, gc, pt.spec.border);
XDrawRectangle(dpy, pix, gc, 4, contentY, kWinWidth - 9, 101);
std::string stStr = status.mmPresent ? status.mmState : "nomodem";
if (!status.mmFailedReason.empty()) {
stStr += " (" + status.mmFailedReason + ")";
}
std::string regStr = status.regStatusLabel();
XftColor* stCol = &colRed;
if (status.mmState == "connected" || status.mmState == "registered") {
if (status.isNetworkRegistered()) {
stCol = &colGreen;
} else if (status.mmState == "searching" || status.mmState == "connecting") {
} else if (status.isNetworkSearching()) {
stCol = &colOrange;
}
drawPango(layoutMain, stCol, 10, contentY + 2, 22,
"State: " + stStr, 185);
"Net: " + regStr, 185);
drawSignalBars(kWinWidth - 32, contentY + 6, status.signalQuality);
std::string opLine = "Operator: " +
(status.operatorName.empty() ? "--" : status.operatorName);
if (!status.operatorCode.empty() &&
status.operatorName != status.operatorCode) {
opLine += " (" + status.operatorCode + ")";
}
if (!status.accessTech.empty()) {
opLine += " [" + status.accessTech + "]";
}
drawPango(layoutSmall, &colLight, 10, contentY + 24, 18, opLine, 220);
std::string sigLine = "Signal: " + std::to_string(status.signalQuality) +
"% | Slot: SIM " +
std::string stStr = status.mmPresent ? status.mmState : "nomodem";
std::string sigLine = "Modem: " + stStr + " | Sig: " +
std::to_string(status.signalQuality) + "% | SIM " +
(status.primaryGwSlot > 0 ? std::to_string(status.primaryGwSlot) : "--");
drawPango(layoutSmall, &colLight, 10, contentY + 42, 18, sigLine, 220);
@@ -1090,6 +1196,15 @@ int runModemApp(int argc, char** argv) {
};
auto showWindow = [&](const std::string& arg = "") {
std::string a = toLowerStr(trimStr(arg));
if (visible &&
(modal == ModalMode::VerifyPin || modal == ModalMode::UnblockPuk) &&
(a == "unlock" || a == "pin" || a == "puk")) {
XRaiseWindow(dpy, win);
grabKeys();
render();
return;
}
closeOtherPlugins("modem");
refreshData(true);
applyOpenArg(arg);
@@ -1308,16 +1423,36 @@ int runModemApp(int argc, char** argv) {
auto triggerSimRowAction = [&](int row) {
if (row == 0 || row == 1) {
targetSlot = row + 1;
int sNum = row + 1;
const auto& s = status.slots[row];
if (s.needsPin() || s.needsPuk()) {
openPinOrPukModalForSlot(sNum);
} else if (s.state == "present" && !s.isActiveProvisioned) {
switchSimSlotNow(sNum);
} else {
targetSlot = sNum;
actionSel = 0;
modal = ModalMode::SimActions;
}
} else if (row == 2) {
int otherSlot = (status.preferredSlot == 1) ? 2 : 1;
if (status.primaryGwSlot == 0) {
if (status.slots[1].state == "present" && status.slots[0].state != "present") {
otherSlot = 2;
} else if (status.slots[0].state == "present" && status.slots[1].state != "present") {
otherSlot = 1;
}
}
switchSimSlotNow(otherSlot);
} else if (row == 3) {
int slot = (status.primaryGwSlot >= 1 && status.primaryGwSlot <= 2)
? status.primaryGwSlot
: status.preferredSlot;
if (status.slots[0].needsPin() || status.slots[0].needsPuk()) {
slot = 1;
} else if (status.slots[1].needsPin() || status.slots[1].needsPuk()) {
slot = 2;
}
openPinOrPukModalForSlot(slot);
} else if (row == 4) {
targetSlot = (status.primaryGwSlot >= 1 && status.primaryGwSlot <= 2)
@@ -1446,14 +1581,18 @@ int runModemApp(int argc, char** argv) {
ussdResponse.clear();
ussdWaitingReply = false;
} else if (radioRowSel == 2) {
bool powerOn = (status.mmPowerState == "on" ||
status.mmState == "registered" ||
status.mmState == "connected" ||
status.mmState == "searching");
bool powerOn = (status.mmPowerState == "on" &&
status.mmState != "disabled" &&
status.mmState != "failed");
setBanner(powerOn ? "Disabling modem..." : "Enabling modem...");
render();
runCaptureCmd(powerOn ? "mmcli -m any -d >/dev/null 2>&1"
: "mmcli -m any -e >/dev/null 2>&1");
if (powerOn) {
runCaptureCmd("mmcli -m any -d >/dev/null 2>&1");
} else {
runCaptureCmd(
"doas -n /usr/libexec/msm-modem-uim-selection boot >/dev/null 2>&1 "
"|| mmcli -m any -e >/dev/null 2>&1");
}
refreshData(false);
runAsyncCmd("sxmo_hook_statusbar.sh modem >/dev/null 2>&1");
} else if (radioRowSel == 3) {
@@ -1494,6 +1633,9 @@ int runModemApp(int argc, char** argv) {
}
};
int autoCheckStage = isPreload ? 0 : 2;
int64_t nextAutoCheckMs = nowMs() + 2500;
if (!isPreload) {
showWindow(openArg);
}
@@ -1841,6 +1983,28 @@ int runModemApp(int argc, char** argv) {
timeoutMs = static_cast<int>(rRem);
}
}
} else if (autoCheckStage < 2) {
int64_t aRem = nextAutoCheckMs - now;
if (aRem <= 0) {
++autoCheckStage;
nextAutoCheckMs = now + 4500;
refreshData(false);
if (status.slots[0].needsPin() || status.slots[0].needsPuk() ||
status.slots[1].needsPin() || status.slots[1].needsPuk() ||
status.mmState == "locked") {
autoCheckStage = 2;
showWindow("unlock");
} else if ((status.slots[0].state == "present" ||
status.slots[1].state == "present") &&
(status.primaryGwSlot == 0 ||
status.mmState == "disabled")) {
runAsyncCmd(
"doas -n /usr/libexec/msm-modem-uim-selection boot >/dev/null "
"2>&1");
}
} else if (timeoutMs < 0 || aRem < timeoutMs) {
timeoutMs = static_cast<int>(aRem);
}
}
struct pollfd pfds[2];
+15 -14
View File
@@ -183,7 +183,14 @@ inline bool sendPluginCommand(const char* name, const std::string& cmdLine) {
return false;
}
std::string fifoPath = getPluginFifoPath(name);
int fd = ::open(fifoPath.c_str(), O_WRONLY | O_NONBLOCK | O_CLOEXEC);
int fd = -1;
for (int attempt = 0; attempt < 6; ++attempt) {
fd = ::open(fifoPath.c_str(), O_WRONLY | O_NONBLOCK | O_CLOEXEC);
if (fd >= 0) {
break;
}
::usleep(25000);
}
if (fd < 0) {
return false;
}
@@ -1651,20 +1658,12 @@ inline std::string sendModemUssd(const std::string& input,
if (clean.empty()) return "Empty USSD request.";
int rc = 0;
std::string cmd;
if (isReply && !isUssdCode(clean)) {
cmd = "mmcli -m any --3gpp-ussd-respond=" + shellQuote(clean) + " 2>&1";
} else {
cmd = "mmcli -m any --3gpp-ussd-cancel >/dev/null 2>&1; "
"mmcli -m any --3gpp-ussd-initiate=" + shellQuote(clean) + " 2>&1";
}
std::string mode = (isReply && !isUssdCode(clean)) ? "respond" : "initiate";
std::string cmd =
"doas -n /usr/libexec/msm-modem-uim-selection ussd " + mode + " " +
shellQuote(clean) + " 2>&1";
std::string out = trimStr(runCaptureCmd(cmd, &rc));
size_t q1 = out.find('\'');
size_t q2 = out.rfind('\'');
if (q1 != std::string::npos && q2 != std::string::npos && q2 > q1) {
out = out.substr(q1 + 1, q2 - q1 - 1);
}
if (out.empty()) {
out = (rc == 0) ? "USSD request completed." : "USSD request failed.";
}
@@ -1698,7 +1697,9 @@ inline std::string sendModemUssd(const std::string& input,
}
inline void cancelModemUssd() {
runAsyncCmd("mmcli -m any --3gpp-ussd-cancel >/dev/null 2>&1");
runAsyncCmd(
"doas -n /usr/libexec/msm-modem-uim-selection ussd cancel >/dev/null 2>&1 "
"|| mmcli -m any --3gpp-ussd-cancel >/dev/null 2>&1");
}
} // namespace bananarchy
+107 -24
View File
@@ -816,6 +816,97 @@ write_str_file(const char *path, const char *val)
return n > 0;
}
static int
dunst_window_visible(void)
{
if (!dpy)
return 0;
Window r_ret, p_ret, *children = NULL;
unsigned int nchildren = 0;
int found = 0;
if (!XQueryTree(dpy, root, &r_ret, &p_ret, &children, &nchildren) || !children)
return 0;
for (unsigned int i = 0; i < nchildren; i++) {
XWindowAttributes wa;
if (!XGetWindowAttributes(dpy, children[i], &wa) || wa.map_state != IsViewable)
continue;
XClassHint ch = { NULL, NULL };
if (XGetClassHint(dpy, children[i], &ch)) {
if ((ch.res_class && strcasecmp(ch.res_class, "Dunst") == 0) ||
(ch.res_name && strcasecmp(ch.res_name, "dunst") == 0))
found = 1;
if (ch.res_name)
XFree(ch.res_name);
if (ch.res_class)
XFree(ch.res_class);
if (found)
break;
}
}
XFree(children);
return found;
}
static void
unmap_dunst_windows(void)
{
if (!dpy)
return;
Window r_ret, p_ret, *children = NULL;
unsigned int nchildren = 0;
int unmapped = 0;
if (!XQueryTree(dpy, root, &r_ret, &p_ret, &children, &nchildren) || !children)
return;
for (unsigned int i = 0; i < nchildren; i++) {
XClassHint ch = { NULL, NULL };
if (XGetClassHint(dpy, children[i], &ch)) {
if ((ch.res_class && strcasecmp(ch.res_class, "Dunst") == 0) ||
(ch.res_name && strcasecmp(ch.res_name, "dunst") == 0)) {
XUnmapWindow(dpy, children[i]);
unmapped = 1;
}
if (ch.res_name)
XFree(ch.res_name);
if (ch.res_class)
XFree(ch.res_class);
}
}
XFree(children);
if (unmapped)
XFlush(dpy);
}
static void
clear_all_notifications_now(void)
{
const char *ndir = getenv("SXMO_NOTIFDIR");
char default_dir[256];
if (!ndir || !ndir[0]) {
const char *home = getenv("HOME");
snprintf(default_dir, sizeof(default_dir), "%s/.local/share/sxmo/notifications",
(home && home[0]) ? home : "/home/user");
ndir = default_dir;
}
DIR *d = opendir(ndir);
if (d) {
struct dirent *ent;
while ((ent = readdir(d)) != NULL) {
if (ent->d_name[0] == '.')
continue;
char fpath[512];
snprintf(fpath, sizeof(fpath), "%s/%s", ndir, ent->d_name);
unlink(fpath);
}
closedir(d);
}
unmap_dunst_windows();
spawn_detached_sh(
"dunstctl close-all >/dev/null 2>&1; "
"dunstctl history-clear >/dev/null 2>&1; "
"pkill -x dunstify >/dev/null 2>&1; "
"sxmo_hook_statusbar.sh notifications >/dev/null 2>&1");
}
static void
handle_builtin_toggle(const char *toggle_name)
{
@@ -914,27 +1005,7 @@ handle_builtin_toggle(const char *toggle_name)
}
spawn_detached_sh("sxmo_hook_statusbar.sh audio");
} else if (strcmp(toggle_name, "notif_clear_all") == 0) {
const char *ndir = getenv("SXMO_NOTIFDIR");
char default_dir[256];
if (!ndir || !ndir[0]) {
const char *home = getenv("HOME");
snprintf(default_dir, sizeof(default_dir), "%s/.local/share/sxmo/notifications",
(home && home[0]) ? home : "/home/user");
ndir = default_dir;
}
DIR *d = opendir(ndir);
if (d) {
struct dirent *ent;
while ((ent = readdir(d)) != NULL) {
if (ent->d_name[0] == '.')
continue;
char fpath[512];
snprintf(fpath, sizeof(fpath), "%s/%s", ndir, ent->d_name);
unlink(fpath);
}
closedir(d);
}
spawn_detached_sh("sxmo_hook_statusbar.sh notifs");
clear_all_notifications_now();
}
}
@@ -1031,7 +1102,10 @@ activate_selection(unsigned int ev_state)
fclose(fp);
}
unlink(fpath);
spawn_detached_sh("sxmo_hook_statusbar.sh notifs");
unmap_dunst_windows();
spawn_detached_sh(
"dunstctl close-all >/dev/null 2>&1; "
"sxmo_hook_statusbar.sh notifications >/dev/null 2>&1");
hide_menu();
menu_mode = MODE_NONE;
if (action_cmd[0] && strcmp(action_cmd, "none") != 0)
@@ -1040,6 +1114,11 @@ activate_selection(unsigned int ev_state)
}
if (strncmp(act, "@toggle:", 8) == 0) {
handle_builtin_toggle(act + 8);
if (strcmp(act + 8, "notif_clear_all") == 0) {
hide_menu();
menu_mode = MODE_NONE;
return;
}
char cur_menu[64];
snprintf(cur_menu, sizeof(cur_menu), "%s", current_builtin_menu);
open_builtin_menu(cur_menu, cur_idx);
@@ -1906,9 +1985,13 @@ open_builtin_menu(const char *menu_name, int keep_idx)
reset_session_defaults(1, "Sys");
append_active_call_entries();
int nnotifs = count_notifications();
int dunst_vis = dunst_window_visible();
if (nnotifs > 0) {
snprintf(label, sizeof(label), "%s Notifications (%d)", ICON_BEL, nnotifs);
add_menu_item(label, "@menu:notifications", 1);
} else if (dunst_vis) {
snprintf(label, sizeof(label), "%s Clear Notification", ICON_BEL);
add_menu_item(label, "@toggle:notif_clear_all", 0);
}
const char *sh = getenv("SHELL");
if (!sh || !sh[0])
@@ -2105,9 +2188,9 @@ open_builtin_menu(const char *menu_name, int keep_idx)
add_menu_item(label, "@close", 0);
} else if (strcmp(menu_name, "notifications") == 0) {
reset_session_defaults(1, "Notifs");
populate_notifications_menu();
snprintf(label, sizeof(label), "%s Clear All Notifications", ICON_CLS);
add_menu_item(label, "@toggle:notif_clear_all", 1);
add_menu_item(label, "@toggle:notif_clear_all", 0);
populate_notifications_menu();
snprintf(label, sizeof(label), "%s System Menu", ICON_MNU);
add_menu_item(label, "@menu:sys", 1);
snprintf(label, sizeof(label), "%s Close Menu", ICON_CLS);