diff --git a/overlay/rootfs/etc/conf.d/msm-modem-uim-selection b/overlay/rootfs/etc/conf.d/msm-modem-uim-selection index fe3d33d..de30efc 100644 --- a/overlay/rootfs/etc/conf.d/msm-modem-uim-selection +++ b/overlay/rootfs/etc/conf.d/msm-modem-uim-selection @@ -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 diff --git a/overlay/rootfs/etc/init.d/msm-modem-uim-selection b/overlay/rootfs/etc/init.d/msm-modem-uim-selection new file mode 100755 index 0000000..40b1aec --- /dev/null +++ b/overlay/rootfs/etc/init.d/msm-modem-uim-selection @@ -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 $? +} diff --git a/overlay/rootfs/home/user/.config/dunst/dunstrc b/overlay/rootfs/home/user/.config/dunst/dunstrc index a8674fc..1510082 100644 --- a/overlay/rootfs/home/user/.config/dunst/dunstrc +++ b/overlay/rootfs/home/user/.config/dunst/dunstrc @@ -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 diff --git a/overlay/rootfs/usr/bin/sxmo_modemaudio.sh b/overlay/rootfs/usr/bin/sxmo_modemaudio.sh index b90a76f..80d4bdc 100755 --- a/overlay/rootfs/usr/bin/sxmo_modemaudio.sh +++ b/overlay/rootfs/usr/bin/sxmo_modemaudio.sh @@ -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 - 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 + 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 } 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 - 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 + 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 } 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 } "$@" diff --git a/overlay/rootfs/usr/bin/sxmo_modemcall.sh b/overlay/rootfs/usr/bin/sxmo_modemcall.sh index 3b54778..b70f573 100755 --- a/overlay/rootfs/usr/bin/sxmo_modemcall.sh +++ b/overlay/rootfs/usr/bin/sxmo_modemcall.sh @@ -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 ;; *) diff --git a/overlay/rootfs/usr/bin/sxmo_modemdial.sh b/overlay/rootfs/usr/bin/sxmo_modemdial.sh index 86b0e6d..28bdb34 100755 --- a/overlay/rootfs/usr/bin/sxmo_modemdial.sh +++ b/overlay/rootfs/usr/bin/sxmo_modemdial.sh @@ -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 </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"[]') - 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" & + 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" + 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() { diff --git a/overlay/rootfs/usr/bin/sxmo_modemmonitor.sh b/overlay/rootfs/usr/bin/sxmo_modemmonitor.sh new file mode 100755 index 0000000..f4806d2 --- /dev/null +++ b/overlay/rootfs/usr/bin/sxmo_modemmonitor.sh @@ -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 diff --git a/overlay/rootfs/usr/bin/sxmo_notifs.sh b/overlay/rootfs/usr/bin/sxmo_notifs.sh index e8056ff..b9cc85b 100755 --- a/overlay/rootfs/usr/bin/sxmo_notifs.sh +++ b/overlay/rootfs/usr/bin/sxmo_notifs.sh @@ -12,189 +12,209 @@ 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" - fi - done + 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 - sxmo_log "Invalid notification file $file found, deleting!" - rm -f "$file" - return - fi +_handle_new_notif_file() { + file="$1" + [ -f "$file" ] || return 0 - sxmo_hook_notification.sh "$file" & + if [ "$(wc -l < "$file" 2>/dev/null || echo 0)" -lt 3 ]; then + sxmo_log "Invalid notification file $file found, deleting!" + rm -f "$file" + return 0 + fi - group="$(awk NR==1 "$file")" - action="$(awk NR==2 "$file")" - msg="$(tail -n+3 "$file" | cut -c1-70)" + sxmo_hook_notification.sh "$file" & - case "$(dunstify --action="default,Open" "$msg")" in - "2") - # - # _clear_notif_group "$group" - # - ;; - "default") - _clear_notif_group "$group" - setsid -f sh -c "$action" > /dev/null 2>&1 - ;; - esac + 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" 2>/dev/null)" in + "2") + ;; + "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() { - _cnt=0 - for _nf in "$SXMO_NOTIFDIR"/*; do - [ -f "$_nf" ] && _cnt=$((_cnt + 1)) - done - sxmo_hook_notifications.sh "$_cnt" + _cnt=0 + for _nf in "$SXMO_NOTIFDIR"/*; do + [ -f "$_nf" ] && _cnt=$((_cnt + 1)) + done + sxmo_hook_notifications.sh "$_cnt" } monitor() { - mkdir -p "$SXMO_NOTIFDIR" + mkdir -p "$SXMO_NOTIFDIR" - find "$SXMO_NOTIFDIR" -type f | while read -r file; do - _handle_new_notif_file "$file" - done + find "$SXMO_NOTIFDIR" -type f 2>/dev/null | while read -r file; do + _handle_new_notif_file "$file" + done - _notifications_hook + _notifications_hook - fifo="$(mktemp -u)" - mkfifo "$fifo" - inotifywait -mq -e attrib,move,delete "$SXMO_NOTIFDIR" >> "$fifo" & - inotif_pid=$! + fifo="$(mktemp -u)" + mkfifo "$fifo" + inotifywait -mq -e close_write,moved_to,delete "$SXMO_NOTIFDIR" >> "$fifo" & + inotif_pid=$! - finish() { - kill "$inotif_pid" - rm "$fifo" - exit - } - trap 'finish' TERM INT EXIT + finish() { + 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 - _handle_new_notif_file "$notif_folder$notif_file" - fi - _notifications_hook - done < "$fifo" + while read -r notif_folder notif_type notif_file; do + if echo "$notif_type" | grep -qE "CLOSE_WRITE|MOVED_TO"; then + _handle_new_notif_file "$notif_folder$notif_file" + fi + _notifications_hook + done < "$fifo" - wait "$inotif_pid" + wait "$inotif_pid" } _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}')" - cat </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 < /dev/null 2>&1 - ;; - esac + case "$picked" in + "$icon_cls Close Menu"|"") + exit + ;; + "$icon_del Clear Notifications") + clear_all + exit + ;; + *) + file="$( + printf "%b" "$choices" | + grep -F "$picked" | + cut -d^ -f2 | + head -n1 + )" + 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 } new() { - while : ; do - case "$1" in - -i) - id="$2" - shift 2 - ;; - -g) - group="$2" - shift 2 - ;; - *) - break - ;; - esac - done + while : ; do + case "$1" in + -i) + id="$2" + shift 2 + ;; + -g) + group="$2" + shift 2 + ;; + *) + break + ;; + esac + done - if [ "$#" -lt 2 ]; then - usage - exit 1 - fi + if [ "$#" -lt 2 ]; then + usage + exit 1 + fi - if [ -z "$id" ]; then - id="$(tr -dc 'a-zA-Z0-9' < /dev/urandom 2>/dev/null | head -c 10)" - fi - if [ -z "$group" ]; then - group="$(tr -dc 'a-zA-Z0-9' < /dev/urandom 2>/dev/null | head -c 10)" - fi + if [ -z "$id" ]; then + id="$(tr -dc 'a-zA-Z0-9' < /dev/urandom 2>/dev/null | head -c 10)" + fi + if [ -z "$group" ]; then + group="$(tr -dc 'a-zA-Z0-9' < /dev/urandom 2>/dev/null | head -c 10)" + fi - action="$1" - msg="$2" + action="$1" + msg="$2" - touch "$SXMO_NOTIFDIR/$id" - printf "%s\n%s\n%b\n" "$group" "$action" "$msg" > "$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 + sxmo_hook_statusbar.sh notifications } usage() { - cat < - Register a new notification + monitor + 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] + Register a new notification EOF } cmd="$1" -shift +shift 2>/dev/null || true case "$cmd" in - monitor) monitor "$@";; - menu) menu "$@";; - new) new "$@";; - *) - usage - exit 1 - ;; + monitor) monitor "$@" ;; + menu) menu "$@" ;; + clear) clear_all ;; + new) new "$@" ;; + *) + usage + exit 1 + ;; esac diff --git a/overlay/rootfs/usr/lib/dwm/plugins/calllog.so b/overlay/rootfs/usr/lib/dwm/plugins/calllog.so index 661947d..eab3d7f 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/calllog.so and b/overlay/rootfs/usr/lib/dwm/plugins/calllog.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/callscreen.so b/overlay/rootfs/usr/lib/dwm/plugins/callscreen.so index 7af3985..07a3cb6 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/callscreen.so and b/overlay/rootfs/usr/lib/dwm/plugins/callscreen.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/camera.so b/overlay/rootfs/usr/lib/dwm/plugins/camera.so index 31a5cd9..002e755 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/camera.so and b/overlay/rootfs/usr/lib/dwm/plugins/camera.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/contacts.so b/overlay/rootfs/usr/lib/dwm/plugins/contacts.so index b0ca5a8..b3c9f89 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/dialer.so b/overlay/rootfs/usr/lib/dwm/plugins/dialer.so index 229c41a..9cb42be 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/dmenu.so b/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so index 0dc1f92..2fcaa97 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so and b/overlay/rootfs/usr/lib/dwm/plugins/dmenu.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/files.so b/overlay/rootfs/usr/lib/dwm/plugins/files.so index f275e07..d6fec41 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 b6c2ad7..55cb688 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 67546b4..9c35911 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/ringtones.so b/overlay/rootfs/usr/lib/dwm/plugins/ringtones.so index 2a2db22..3440c43 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/ringtones.so and b/overlay/rootfs/usr/lib/dwm/plugins/ringtones.so differ diff --git a/overlay/rootfs/usr/lib/dwm/plugins/sms.so b/overlay/rootfs/usr/lib/dwm/plugins/sms.so index 17fca6a..90d5cdc 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 fb24bab..18d27d0 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/dwm/plugins/wallpapers.so b/overlay/rootfs/usr/lib/dwm/plugins/wallpapers.so index 147c381..4f97441 100755 Binary files a/overlay/rootfs/usr/lib/dwm/plugins/wallpapers.so and b/overlay/rootfs/usr/lib/dwm/plugins/wallpapers.so differ diff --git a/overlay/rootfs/usr/libexec/msm-modem-uim-selection b/overlay/rootfs/usr/libexec/msm-modem-uim-selection index f943191..4d7f6c9 100755 --- a/overlay/rootfs/usr/libexec/msm-modem-uim-selection +++ b/overlay/rootfs/usr/libexec/msm-modem-uim-selection @@ -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 - 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 [ -n "$chosen_aid" ]; then + echo "Selecting AID $chosen_aid on slot $chosen_slot" + 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 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 &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" --pin="$pin" >/dev/null 2>&1; then - exit 0 - fi + verified=0 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 - exit 0 + 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 + verified=1 + fi + fi + 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 &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" --puk="$puk" --pin="$newpin" >/dev/null 2>&1; then - exit 0 - fi + unblocked=0 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 - exit 0 + 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 + unblocked=1 + fi + fi + 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 " >&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> " >&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 diff --git a/overlay/rootfs/usr/share/sxmo/appcfg/dunst.conf b/overlay/rootfs/usr/share/sxmo/appcfg/dunst.conf index a8674fc..1510082 100644 --- a/overlay/rootfs/usr/share/sxmo/appcfg/dunst.conf +++ b/overlay/rootfs/usr/share/sxmo/appcfg/dunst.conf @@ -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 diff --git a/overlay/rootfs/usr/share/sxmo/default_hooks/sxmo_hook_modem.sh b/overlay/rootfs/usr/share/sxmo/default_hooks/sxmo_hook_modem.sh index 03c96d8..e1125ac 100755 --- a/overlay/rootfs/usr/share/sxmo/default_hooks/sxmo_hook_modem.sh +++ b/overlay/rootfs/usr/share/sxmo/default_hooks/sxmo_hook_modem.sh @@ -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 diff --git a/overlay/rootfs/usr/share/sxmo/default_hooks/sxmo_hook_statusbar.sh b/overlay/rootfs/usr/share/sxmo/default_hooks/sxmo_hook_statusbar.sh index fcbcdcc..5b24951 100755 --- a/overlay/rootfs/usr/share/sxmo/default_hooks/sxmo_hook_statusbar.sh +++ b/overlay/rootfs/usr/share/sxmo/default_hooks/sxmo_hook_statusbar.sh @@ -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 diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so index 661947d..eab3d7f 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/calllog.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/callscreen.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/callscreen.so index 7af3985..07a3cb6 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/callscreen.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/callscreen.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/camera.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/camera.so index 31a5cd9..002e755 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/camera.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/camera.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 b0ca5a8..b3c9f89 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/dialer.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dialer.so index 229c41a..9cb42be 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/dmenu.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so index 0dc1f92..2fcaa97 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/dmenu.so differ diff --git a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/files.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/files.so index f275e07..d6fec41 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 b6c2ad7..55cb688 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 67546b4..9c35911 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/ringtones.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/ringtones.so index 2a2db22..3440c43 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/ringtones.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/ringtones.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 17fca6a..90d5cdc 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 fb24bab..18d27d0 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/prebuilt/sxmo-x11/usr/lib/dwm/plugins/wallpapers.so b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/wallpapers.so index 147c381..4f97441 100755 Binary files a/prebuilt/sxmo-x11/usr/lib/dwm/plugins/wallpapers.so and b/prebuilt/sxmo-x11/usr/lib/dwm/plugins/wallpapers.so differ diff --git a/src/dwm-plugins/modem_plugin.cpp b/src/dwm-plugins/modem_plugin.cpp index 11e3cb1..3ad6723 100644 --- a/src/dwm-plugins/modem_plugin.cpp +++ b/src/dwm-plugins/modem_plugin.cpp @@ -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(::getpid())); int fifoFd = setupPluginDaemonFifo("modem"); + writeIntFile(daemonPidPath, static_cast(::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; - line2Col = &colGreen; + 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; - actionSel = 0; - modal = ModalMode::SimActions; + 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(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(aRem); + } } struct pollfd pfds[2]; diff --git a/src/dwm-plugins/phone_data.h b/src/dwm-plugins/phone_data.h index f7813c2..3c997c7 100644 --- a/src/dwm-plugins/phone_data.h +++ b/src/dwm-plugins/phone_data.h @@ -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 diff --git a/src/sxmo-dmenu/dmenu.c b/src/sxmo-dmenu/dmenu.c index 8baaa91..d9c1332 100644 --- a/src/sxmo-dmenu/dmenu.c +++ b/src/sxmo-dmenu/dmenu.c @@ -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);