Fix mobile data static IP bearer, call recording, earpiece audio, and ringtone/vibration

This commit is contained in:
Luxferre
2026-10-25 20:23:17 +02:00
parent 21dfb187be
commit a8a28fa685
23 changed files with 624 additions and 212 deletions
@@ -30,5 +30,12 @@ start() {
if [ -n "$QMICLI_MODEM" ]; then
$QMICLI_MODEM --dpm-open-port=hw-data-ep-type=bam-dmux,hw-data-ep-iface-number=1,hw-data-rx-id=1,hw-data-tx-id=1 >/dev/null 2>&1 || true
fi
for f in /etc/NetworkManager/system-connections/*.nmconnection; do
[ -f "$f" ] || continue
if grep -q "^type=gsm" "$f" 2>/dev/null; then
sed -i '/^\[ipv6\]/,/^\[/ s/^method=auto$/method=ignore/' "$f" 2>/dev/null || true
fi
done
eend 0
}
@@ -0,0 +1,4 @@
SXMO_RINGTIME=20
SXMO_RINGNUMBER=10
SXMO_VIBRATE_STRENGTH=2
SXMO_RINGVOL=100
@@ -0,0 +1 @@
/usr/share/sxmo/ringtones/Banana_Chime.wav
+5 -2
View File
@@ -2,6 +2,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2022 Sxmo Contributors
export PATH="${XDG_CONFIG_HOME:-${HOME:-/home/user}/.config}/sxmo/hooks:/usr/share/sxmo/default_hooks:$PATH"
# include common definitions
# shellcheck source=configs/default_hooks/sxmo_hook_icons.sh
. sxmo_hook_icons.sh
@@ -107,9 +109,10 @@ checkforfinishedcalls() {
# If it was the last call
if ! sxmo_modemcall.sh list_active_calls | grep -q .; then
# Cleanup
sxmo_jobs.sh stop ringing
rm -f "$XDG_RUNTIME_DIR/sxmo_calls/"* 2>/dev/null || true
if [ "$STATE" != muted ]; then
sxmo_vibrate 1000 "${SXMO_VIBRATE_STRENGTH:-1}" &
if [ "$STATE" != muted ] && [ ! -f "$XDG_CONFIG_HOME/sxmo/.novibrate" ]; then
sxmo_vibrate 600 &
fi
sxmo_jobs.sh stop incall_menu
sxmo_jobs.sh stop proximity_lock
+31 -21
View File
@@ -6,19 +6,26 @@ export PATH="${XDG_CONFIG_HOME:-$HOME/.config}/sxmo/hooks:/usr/share/sxmo/defaul
. sxmo_common.sh
enable_call_audio_mode() {
pactl set-card-profile alsa_card.comprC0 off >/dev/null 2>&1 || true
pactl set-card-profile alsa_card.platform-7702000.sound "HiFi (Earpiece, Mic1)" >/dev/null 2>&1 || true
wpctl set-mute @DEFAULT_AUDIO_SINK@ 0 >/dev/null 2>&1 || true
wpctl set-volume @DEFAULT_AUDIO_SINK@ 100% >/dev/null 2>&1 || true
if command -v amixer >/dev/null 2>&1; then
amixer -c 0 sset 'SPK DAC Switch' off >/dev/null 2>&1 || true
amixer -c 0 sset 'RX3 MIX1 INP1' 'ZERO' >/dev/null 2>&1 || true
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 'MultiMedia2 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 'CIC1 MUX' 'AMIC' >/dev/null 2>&1 || true
amixer -c 0 sset 'DEC1 MUX' 'ADC1' >/dev/null 2>&1 || true
amixer -c 0 sset 'ADC1 Volume' 6 >/dev/null 2>&1 || true
amixer -c 0 sset 'RX1 Digital Volume' 84 >/dev/null 2>&1 || true
amixer -c 0 cset "name='SPK DAC Switch'" 0 >/dev/null 2>&1 || true
amixer -c 0 cset "name='RX3 MIX1 INP1'" 'ZERO' >/dev/null 2>&1 || true
amixer -c 0 cset "name='RX3 Digital Volume'" 0 >/dev/null 2>&1 || true
amixer -c 0 cset "name='PRI_MI2S_RX Voice Mixer VoiceMMode1'" 1 >/dev/null 2>&1 || true
amixer -c 0 cset "name='VoiceMMode1 Capture Mixer TERT_MI2S_TX'" 1 >/dev/null 2>&1 || true
amixer -c 0 cset "name='MultiMedia2 Mixer TERT_MI2S_TX'" 0 >/dev/null 2>&1 || true
amixer -c 0 cset "name='RX1 MIX1 INP1'" 'RX1' >/dev/null 2>&1 || true
amixer -c 0 cset "name='RDAC2 MUX'" 'RX1' >/dev/null 2>&1 || true
amixer -c 0 cset "name='EAR_S'" 'Switch' >/dev/null 2>&1 || true
amixer -c 0 cset "name='RX1 Mute Switch'" 0 >/dev/null 2>&1 || true
amixer -c 0 cset "name='RX1 Digital Volume'" 88 >/dev/null 2>&1 || true
amixer -c 0 cset "name='CIC1 MUX'" 'AMIC' >/dev/null 2>&1 || true
amixer -c 0 cset "name='DEC1 MUX'" 'ADC1' >/dev/null 2>&1 || true
amixer -c 0 cset "name='ADC1 Volume'" 8 >/dev/null 2>&1 || true
amixer -c 0 cset "name='TX1 Digital Volume'" 88 >/dev/null 2>&1 || true
fi
sxmo_log "Enabled ALSA voice call audio mode (Earpiece)."
sxmo_hook_statusbar.sh volume >/dev/null 2>&1 || true
@@ -38,18 +45,21 @@ disable_call_audio_mode() {
pactl set-card-profile alsa_card.platform-7702000.sound "HiFi (Mic1, Speaker)" >/dev/null 2>&1 || true
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
amixer -c 0 sset 'EAR_S' 'ZERO' >/dev/null 2>&1 || true
amixer -c 0 sset 'RX1 MIX1 INP1' 'ZERO' >/dev/null 2>&1 || true
amixer -c 0 sset 'RX3 MIX1 INP1' 'RX1' >/dev/null 2>&1 || true
amixer -c 0 cset "name='PRI_MI2S_RX Voice Mixer VoiceMMode1'" 0 >/dev/null 2>&1 || true
amixer -c 0 cset "name='VoiceMMode1 Capture Mixer TERT_MI2S_TX'" 0 >/dev/null 2>&1 || true
amixer -c 0 cset "name='MultiMedia2 Mixer TERT_MI2S_TX'" 1 >/dev/null 2>&1 || true
amixer -c 0 cset "name='EAR_S'" 'ZERO' >/dev/null 2>&1 || true
amixer -c 0 cset "name='RX1 MIX1 INP1'" 'ZERO' >/dev/null 2>&1 || true
amixer -c 0 cset "name='RX1 Digital Volume'" 84 >/dev/null 2>&1 || true
amixer -c 0 cset "name='TX1 Digital Volume'" 84 >/dev/null 2>&1 || true
amixer -c 0 cset "name='RX3 MIX1 INP1'" 'RX1' >/dev/null 2>&1 || true
if [ "$spk_vol" -gt 0 ]; then
rx3_hw=$(( 60 + spk_vol * 32 / 100 ))
amixer -c 0 sset 'SPK DAC Switch' on >/dev/null 2>&1 || true
amixer -c 0 sset 'RX3 Digital Volume' "$rx3_hw" >/dev/null 2>&1 || true
amixer -c 0 cset "name='SPK DAC Switch'" 1 >/dev/null 2>&1 || true
amixer -c 0 cset "name='RX3 Digital Volume'" "$rx3_hw" >/dev/null 2>&1 || true
else
amixer -c 0 sset 'SPK DAC Switch' off >/dev/null 2>&1 || true
amixer -c 0 sset 'RX3 Digital Volume' 0 >/dev/null 2>&1 || true
amixer -c 0 cset "name='SPK DAC Switch'" 0 >/dev/null 2>&1 || true
amixer -c 0 cset "name='RX3 Digital Volume'" 0 >/dev/null 2>&1 || true
fi
fi
wpctl set-mute @DEFAULT_AUDIO_SINK@ "$([ "$spk_vol" -le 0 ] && echo 1 || echo 0)" >/dev/null 2>&1 || true
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,27 @@
SectionDevice."Earpiece" {
Comment "Earpiece playback"
ConflictingDevice [
"Headphones"
]
EnableSequence [
cset "name='RX1 MIX1 INP1' RX1"
cset "name='RDAC2 MUX' RX1"
cset "name='EAR_S' 1"
## gain to +6dB
cset "name='RX1 Digital Volume' 90"
]
DisableSequence [
cset "name='RX1 Digital Volume' 0"
cset "name='EAR_S' 0"
cset "name='RX1 MIX1 INP1' ZERO"
]
Value {
PlaybackPCM "${var:WcdPlaybackPCM}"
PlaybackChannels 2
PlaybackPriority 100
}
}
@@ -4,13 +4,14 @@
# Copyright 2022 Sxmo Contributors
# This script is executed (asynchronously) when you get an incoming call
# You can use it to play a ring tone
# $1 = Contact Name or Number (if not in contacts)
# shellcheck source=scripts/core/sxmo_common.sh
. sxmo_common.sh
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-${HOME:-/home/user}/.config}"
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
if [ -z "${SXMO_VIBRATE_DEV:-}" ] && [ ! -e /dev/input/by-path/platform-vibrator-event ]; then
for vdev in /dev/input/by-path/*vibrator*-event; do
if [ -e "$vdev" ]; then
@@ -25,33 +26,61 @@ if [ -f "$XDG_CONFIG_HOME/sxmo/ringsettings" ]; then
. "$XDG_CONFIG_HOME/sxmo/ringsettings"
fi
# pm8xxx-vib shifts FF_RUMBLE strong_magnitude right by 8 bits (valid 256..65535)
case "${SXMO_VIBRATE_STRENGTH:-}" in
1) SXMO_VIBRATE_STRENGTH=8000 ;;
2|"") SXMO_VIBRATE_STRENGTH=32000 ;;
3) SXMO_VIBRATE_STRENGTH=65535 ;;
case "${SXMO_VIBRATE_STRENGTH:-2}" in
1) SXMO_VIBRATE_STRENGTH=1 ;;
3) SXMO_VIBRATE_STRENGTH=3 ;;
*)
if [ "${SXMO_VIBRATE_STRENGTH:-2}" -gt 45000 ] 2>/dev/null; then
SXMO_VIBRATE_STRENGTH=3
elif [ "${SXMO_VIBRATE_STRENGTH:-2}" -gt 3 ] && [ "${SXMO_VIBRATE_STRENGTH:-2}" -le 16000 ] 2>/dev/null; then
SXMO_VIBRATE_STRENGTH=1
else
SXMO_VIBRATE_STRENGTH=2
fi
;;
esac
export SXMO_VIBRATE_STRENGTH
if [ -f "$XDG_CONFIG_HOME/sxmo/ringtone" ]; then
custom_ring="$(head -n 1 "$XDG_CONFIG_HOME/sxmo/ringtone" 2>/dev/null)"
custom_ring="$(head -n 1 "$XDG_CONFIG_HOME/sxmo/ringtone" 2>/dev/null | tr -d '\r')"
if [ -n "$custom_ring" ] && [ -f "$custom_ring" ]; then
SXMO_RINGTONE="$custom_ring"
fi
fi
# Only vibrate if you already got an active call
if sxmo_modemcall.sh list_active_calls \
| grep -v ringing-in \
| grep -q .; then
sxmo_vibrate 1500 "${SXMO_VIBRATE_STRENGTH:-1}"
exit
if [ -z "${SXMO_RINGTONE:-}" ] || [ ! -f "${SXMO_RINGTONE:-}" ]; then
if [ -f /usr/share/sxmo/ringtones/Banana_Chime.wav ]; then
SXMO_RINGTONE="/usr/share/sxmo/ringtones/Banana_Chime.wav"
elif [ -f /usr/share/sxmo/ring.ogg ]; then
SXMO_RINGTONE="/usr/share/sxmo/ring.ogg"
else
for f in /usr/share/sxmo/ringtones/*; do
if [ -f "$f" ]; then
SXMO_RINGTONE="$f"
break
fi
done
fi
fi
export SXMO_RINGTONE
# DND / BUSY MODE
if [ -f "$XDG_RUNTIME_DIR/sxmo.busy" ]; then
exit 0
fi
if [ -f "$XDG_CONFIG_HOME/sxmo/.busy" ]; then
end="$(cat "$XDG_CONFIG_HOME/sxmo/.busy" 2>/dev/null)"
if [ -z "$end" ] || [ "$(date +%s)" -lt "$end" ]; then
exit 0
fi
fi
# Shallow if you have more than one ringing call
if ! sxmo_modemcall.sh list_active_calls \
| grep -c ringing-in \
| grep -q 1; then
exit
# Only vibrate once if a call is already active
if [ "$(cat /tmp/sxmo_active_call 2>/dev/null)" = "2" ]; then
if [ ! -f "$XDG_CONFIG_HOME/sxmo/.novibrate" ]; then
sxmo_vibrate 1500 "$SXMO_VIBRATE_STRENGTH"
fi
exit 0
fi
case "${SXMO_RINGVOL:-100}" in
@@ -59,90 +88,167 @@ case "${SXMO_RINGVOL:-100}" in
esac
[ "$SXMO_RINGVOL" -gt 100 ] && SXMO_RINGVOL=100
SAVED_RX3="$(amixer -c 0 cget "name='RX3 Digital Volume'" 2>/dev/null | awk -F= '/: values=/ {print $2; exit}')"
SAVED_SPK_VOL=100
SAVED_SPK_VOL=85
if [ -r "$XDG_CONFIG_HOME/sxmo/speaker_vol" ]; then
read -r SAVED_SPK_VOL < "$XDG_CONFIG_HOME/sxmo/speaker_vol" 2>/dev/null || SAVED_SPK_VOL=100
read -r SAVED_SPK_VOL < "$XDG_CONFIG_HOME/sxmo/speaker_vol" 2>/dev/null || SAVED_SPK_VOL=85
fi
case "$SAVED_SPK_VOL" in ''|*[!0-9]*) SAVED_SPK_VOL=100 ;; esac
case "$SAVED_SPK_VOL" in ''|*[!0-9]*) SAVED_SPK_VOL=85 ;; esac
SAVED_RX3=$(( 60 + SAVED_SPK_VOL * 32 / 100 ))
RING_PID=""
VIB_PID=""
is_call_picked_up() {
[ "$(cat /tmp/sxmo_active_call 2>/dev/null)" = "2" ] && return 0
for f in "$XDG_RUNTIME_DIR"/sxmo_calls/*.pickedupcall "$XDG_RUNTIME_DIR"/sxmo_calls/*.initiatedcall; do
[ -e "$f" ] && return 0
done
return 1
}
finish() {
trap - INT TERM EXIT
jobs -p | xargs -r kill 2>/dev/null || true
wait 2>/dev/null || true
if [ -n "${SAVED_RX3:-}" ]; then
[ -n "$RING_PID" ] && pkill -TERM -P "$RING_PID" 2>/dev/null || true
[ -n "$VIB_PID" ] && pkill -TERM -P "$VIB_PID" 2>/dev/null || true
[ -n "$RING_PID" ] && kill -TERM "$RING_PID" 2>/dev/null || true
[ -n "$VIB_PID" ] && kill -TERM "$VIB_PID" 2>/dev/null || true
pkill -TERM -P "$$" 2>/dev/null || true
if ! is_call_picked_up; then
amixer -c 0 cset "name='RX3 Digital Volume'" "$SAVED_RX3" >/dev/null 2>&1 || true
wpctl set-volume @DEFAULT_AUDIO_SINK@ "${SAVED_SPK_VOL}%" >/dev/null 2>&1 || true
fi
wpctl set-volume @DEFAULT_AUDIO_SINK@ "${SAVED_SPK_VOL}%" >/dev/null 2>&1 || true
exit
exit 0
}
pause_media_async() {
if [ -p /tmp/dwm_plugin_music.fifo ]; then
echo "PAUSE" > /tmp/dwm_plugin_music.fifo 2>/dev/null || true
fi
mpc -q pause >/dev/null 2>&1 &
sxmo_playerctl.sh pause_all >/dev/null 2>&1 &
}
ring() {
if [ "${SXMO_RINGVOL:-100}" -le 0 ]; then
if [ "${SXMO_RINGVOL:-100}" -le 0 ] || [ -z "${SXMO_RINGTONE:-}" ] || [ ! -f "$SXMO_RINGTONE" ]; then
return 0
fi
ring_rx3=$(( 60 + SXMO_RINGVOL * 34 / 100 ))
mpv_vol=$(( SXMO_RINGVOL * 130 / 100 ))
amixer -c 0 cset "name='SPK DAC Switch'" 1 >/dev/null 2>&1 || true
amixer -c 0 cset "name='RX3 MIX1 INP1'" 3 >/dev/null 2>&1 || true
amixer -c 0 cset "name='RX3 Digital Volume'" "$ring_rx3" >/dev/null 2>&1 || true
wpctl set-mute @DEFAULT_AUDIO_SINK@ 0 >/dev/null 2>&1 || true
wpctl set-volume @DEFAULT_AUDIO_SINK@ 100% >/dev/null 2>&1 || true
mpv --no-resume-playback --quiet --no-video \
--ao=pipewire,pulse,alsa --audio-channels=stereo --audio-samplerate=48000 \
--volume-max=150 --volume="$mpv_vol" \
--loop="${SXMO_RINGNUMBER:-10}" "$SXMO_RINGTONE" >/dev/null &
pw_vol="$(awk -v v="$SXMO_RINGVOL" 'BEGIN { printf "%.2f", v / 100.0 }')"
(
rchild=""
vhelper=""
trap '[ -n "$rchild" ] && kill -TERM "$rchild" 2>/dev/null; [ -n "$vhelper" ] && kill -TERM "$vhelper" 2>/dev/null; exit 0' INT TERM EXIT
amixer -c 0 -s >/dev/null 2>&1 <<EOF || true
cset "name='EAR_S'" 0
cset "name='RX1 MIX1 INP1'" 0
cset "name='PRI_MI2S_RX Audio Mixer MultiMedia1'" 1
cset "name='SPK DAC Switch'" 1
cset "name='RX3 MIX1 INP1'" 3
cset "name='RX3 Digital Volume'" $ring_rx3
EOF
wpctl set-mute @DEFAULT_AUDIO_SINK@ 0 >/dev/null 2>&1 || true
wpctl set-volume @DEFAULT_AUDIO_SINK@ "${SXMO_RINGVOL}%" >/dev/null 2>&1 || true
(
for d in 0.15 0.40; do
sleep "$d" || exit 0
is_call_picked_up && exit 0
amixer -c 0 -s >/dev/null 2>&1 <<EOF || true
cset "name='SPK DAC Switch'" 1
cset "name='RX3 MIX1 INP1'" 3
cset "name='RX3 Digital Volume'" $ring_rx3
EOF
wpctl set-volume @DEFAULT_AUDIO_SINK@ "${SXMO_RINGVOL}%" >/dev/null 2>&1 || true
done
) &
vhelper="$!"
ext="$(printf '%s' "${SXMO_RINGTONE##*.}" | tr '[:upper:]' '[:lower:]')"
use_pw=0
case "$ext" in
wav|ogg|oga|mp3|flac|aiff|aif)
command -v pw-play >/dev/null 2>&1 && use_pw=1
;;
esac
loops="${SXMO_RINGNUMBER:-10}"
if [ "$use_pw" -eq 1 ]; then
i=0
while [ "$i" -lt "$loops" ]; do
is_call_picked_up && break
pw-play --volume="$pw_vol" "$SXMO_RINGTONE" >/dev/null 2>&1 &
rchild="$!"
wait "$rchild" 2>/dev/null
rc="$?"
rchild=""
if [ "$rc" -ne 0 ]; then
use_pw=0
break
fi
i=$(( i + 1 ))
sleep 0.2 &
rchild="$!"
wait "$rchild" 2>/dev/null || break
rchild=""
done
fi
if [ "$use_pw" -eq 0 ] && ! is_call_picked_up; then
mpv --no-config --no-terminal --no-resume-playback --quiet --vo=null --no-video \
--ao=pipewire,pulse,alsa --audio-channels=stereo --audio-samplerate=48000 \
--volume-max=150 --volume="$SXMO_RINGVOL" \
--loop="$loops" "$SXMO_RINGTONE" </dev/null >/dev/null 2>&1 &
rchild="$!"
wait "$rchild" 2>/dev/null || true
rchild=""
fi
) &
RING_PID="$!"
}
vibrate() {
while : ; do
trap 'finish' INT TERM EXIT
sxmo_vibrate 1500 "${SXMO_VIBRATE_STRENGTH:-1}" &
wait "$!"
(
vchild=""
trap '[ -n "$vchild" ] && kill -TERM "$vchild" 2>/dev/null; exit 0' INT TERM EXIT
while : ; do
is_call_picked_up && exit 0
sxmo_vibrate 1500 "$SXMO_VIBRATE_STRENGTH" &
vchild="$!"
wait "$vchild" 2>/dev/null || exit 0
vchild=""
sleep 0.5 &
wait "$!"
done &
sleep 0.5 &
vchild="$!"
wait "$vchild" 2>/dev/null || exit 0
vchild=""
done
) &
VIB_PID="$!"
}
# SILENT MODE
if [ -f "$XDG_CONFIG_HOME"/sxmo/.busy ]; then
end="$(cat "$XDG_CONFIG_HOME"/sxmo/.busy)"
if [ -z "$end" ] || [ "$(date +%s)" -lt "$end" ]; then
exit
fi
fi
trap 'finish' INT TERM EXIT
# RING & VIBRATE MODE (DEFAULT)
if [ ! -f "$XDG_CONFIG_HOME"/sxmo/.noring ] && [ ! -f "$XDG_CONFIG_HOME"/sxmo/.novibrate ]; then
sxmo_log "RING AND VIBRATE"
sxmo_playerctl.sh pause_all
if [ -p /tmp/dwm_plugin_music.fifo ]; then
echo "PAUSE" > /tmp/dwm_plugin_music.fifo 2>/dev/null || true
fi
ring
if [ ! -f "$XDG_CONFIG_HOME/sxmo/.noring" ] && [ ! -f "$XDG_CONFIG_HOME/sxmo/.novibrate" ]; then
sxmo_log "RING AND VIBRATE ($SXMO_RINGTONE, vol=${SXMO_RINGVOL}%, vib=${SXMO_VIBRATE_STRENGTH})"
pause_media_async
vibrate
ring
# RING-ONLY MODE
elif [ ! -f "$XDG_CONFIG_HOME"/sxmo/.noring ] && [ -f "$XDG_CONFIG_HOME"/sxmo/.novibrate ]; then
sxmo_log "RING ONLY"
sxmo_playerctl.sh pause_all
if [ -p /tmp/dwm_plugin_music.fifo ]; then
echo "PAUSE" > /tmp/dwm_plugin_music.fifo 2>/dev/null || true
fi
elif [ ! -f "$XDG_CONFIG_HOME/sxmo/.noring" ] && [ -f "$XDG_CONFIG_HOME/sxmo/.novibrate" ]; then
sxmo_log "RING ONLY ($SXMO_RINGTONE, vol=${SXMO_RINGVOL}%)"
pause_media_async
ring
# VIBRATE-ONLY MODE
elif [ ! -f "$XDG_CONFIG_HOME"/sxmo/.novibrate ] && [ -f "$XDG_CONFIG_HOME"/sxmo/.noring ]; then
sxmo_log "VIBRATE ONLY"
elif [ ! -f "$XDG_CONFIG_HOME/sxmo/.novibrate" ] && [ -f "$XDG_CONFIG_HOME/sxmo/.noring" ]; then
sxmo_log "VIBRATE ONLY (vib=${SXMO_VIBRATE_STRENGTH})"
vibrate
fi
trap 'finish' INT TERM EXIT
sleep "${SXMO_RINGTIME:-20}" &
wait "$!"
finish
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+157 -57
View File
@@ -192,10 +192,12 @@ std::string sanitizeForFilename(const std::string& raw) {
}
}
if (out.empty()) {
return "unknown";
return "voice";
}
if (out.size() > 20) {
out = out.substr(0, 20);
if (out.size() > 10 && out[0] == '+') {
out = out.substr(out.size() - 10);
} else if (out.size() > 10) {
out = out.substr(0, 10);
}
return out;
}
@@ -217,7 +219,7 @@ void finalizeWavHeader(const std::string& path) {
return;
}
uint8_t hdr[64] = {0};
ssize_t n = :: pread(fd, hdr, sizeof(hdr), 0);
ssize_t n = ::pread(fd, hdr, sizeof(hdr), 0);
if (n < 44 || std::memcmp(hdr, "RIFF", 4) != 0 ||
std::memcmp(hdr + 8, "WAVE", 4) != 0) {
::close(fd);
@@ -237,6 +239,12 @@ void finalizeWavHeader(const std::string& path) {
};
(void)::pwrite(fd, b, 4, 4);
}
ssize_t dataOff = -1;
uint32_t dataBytes = 0;
uint16_t audioFmt = static_cast<uint16_t>(hdr[20]) |
(static_cast<uint16_t>(hdr[21]) << 8);
uint16_t bitsPerSample = static_cast<uint16_t>(hdr[34]) |
(static_cast<uint16_t>(hdr[35]) << 8);
for (ssize_t off = 12; off + 8 <= n;) {
uint32_t chunkLen = static_cast<uint32_t>(hdr[off + 4]) |
(static_cast<uint32_t>(hdr[off + 5]) << 8) |
@@ -244,6 +252,8 @@ void finalizeWavHeader(const std::string& path) {
(static_cast<uint32_t>(hdr[off + 7]) << 24);
if (std::memcmp(hdr + off, "data", 4) == 0) {
uint32_t expectedData = static_cast<uint32_t>(st.st_size - (off + 8));
dataOff = off + 8;
dataBytes = expectedData;
if (chunkLen == 0 || chunkLen != expectedData) {
uint8_t b[4] = {
static_cast<uint8_t>(expectedData & 0xff),
@@ -258,6 +268,33 @@ void finalizeWavHeader(const std::string& path) {
if (chunkLen == 0) break;
off += 8 + static_cast<ssize_t>((chunkLen + 1) & ~1u);
}
// Peak-normalize quiet 16-bit signed PCM recordings so playback is clearly audible
if (audioFmt == 1 && bitsPerSample == 16 && dataOff >= 44 &&
dataBytes >= 2 && dataBytes <= 32 * 1024 * 1024) {
size_t numSamples = dataBytes / 2;
std::vector<int16_t> pcm(numSamples);
ssize_t rd = ::pread(fd, pcm.data(), numSamples * sizeof(int16_t), dataOff);
if (rd == static_cast<ssize_t>(numSamples * sizeof(int16_t))) {
int maxAbs = 0;
size_t skip = std::min<size_t>(numSamples / 4, 400);
for (size_t i = skip; i < numSamples; ++i) {
int v = std::abs(static_cast<int>(pcm[i]));
if (v > maxAbs) {
maxAbs = v;
}
}
if (maxAbs >= 32 && maxAbs < 18000) {
double gain = std::min(24.0, 24000.0 / static_cast<double>(maxAbs));
if (gain > 1.15) {
for (size_t i = 0; i < numSamples; ++i) {
int scaled = static_cast<int>(std::lround(pcm[i] * gain));
pcm[i] = static_cast<int16_t>(std::clamp(scaled, -32767, 32767));
}
(void)::pwrite(fd, pcm.data(), numSamples * sizeof(int16_t), dataOff);
}
}
}
}
::fsync(fd);
::close(fd);
}
@@ -434,7 +471,7 @@ int callscreen_run_main(int argc, char** argv) {
bool ringMuted = false;
bool micMuted = false;
bool speakerOn = false;
int callVolumePct = 80;
int callVolumePct = 85;
int selectedRow = 0;
int64_t callStartMs = 0;
@@ -446,6 +483,8 @@ int callscreen_run_main(int argc, char** argv) {
int64_t recordStartMs = 0;
std::string recordFilePath;
std::string recordFileName;
int lastRecordedSec = 0;
int recordedClipCount = 0;
bool visible = false;
int64_t openStartMs = 0;
@@ -454,6 +493,22 @@ int callscreen_run_main(int argc, char** argv) {
int softkeyPending = 0;
int64_t softkeyDeadlineMs = 0;
auto pctToHwVol = [](int pct, bool spk) -> long {
int c = std::clamp(pct, 5, 100);
// RX1/RX3 Digital Volume: 84 = 0 dB, 88 = +4 dB, 92 = +8 dB, 94 = +10 dB
return spk ? (68 + (c * 26) / 100) : (68 + (c * 24) / 100);
};
auto hwVolToPct = [](long hwVol, bool spk) -> int {
if (hwVol < 68) {
return 85;
}
int span = spk ? 26 : 24;
int pct = static_cast<int>(std::lround(((hwVol - 68) * 100.0) / span));
pct = std::clamp(pct, 5, 100);
return std::max(5, ((pct + 2) / 5) * 5);
};
auto refreshHardwareAudioState = [&]() {
unsigned int dec1 = alsa.getEnum("DEC1 MUX", 1);
if (dec1 > 0) {
@@ -464,27 +519,18 @@ int callscreen_run_main(int argc, char** argv) {
savedMicMux = static_cast<unsigned int>(std::max(1, readIntFile("/tmp/xdrawer_mic_mux", 1)));
micMuted = true;
}
bool spkSwitch = alsa.getBoolean("SPK DAC Switch", false);
bool earSwitch = (alsa.getEnum("EAR_S", 1) != 0);
speakerOn = (spkSwitch && !earSwitch);
long rawVol = speakerOn ? alsa.getInteger("RX3 Digital Volume", 68)
: alsa.getInteger("RX1 Digital Volume", 68);
if (rawVol <= 0) {
rawVol = alsa.getInteger("RX1 Digital Volume", 68);
}
callVolumePct = std::clamp(static_cast<int>(std::lround((rawVol * 100.0) / 84.0)), 0, 100);
callVolumePct = (callVolumePct / 5) * 5;
if (callVolumePct == 0) {
callVolumePct = 75;
int savedVol = readIntFile("/tmp/sxmo_call_vol", -1);
if (savedVol >= 5 && savedVol <= 100) {
callVolumePct = savedVol;
}
};
auto setMicMuted = [&](bool mute) {
micMuted = mute;
alsa.setEnum("DEC1 MUX", mute ? 0 : (savedMicMux > 0 ? savedMicMux : 1));
alsa.setInteger("ADC1 Volume", mute ? 0 : 6);
alsa.setInteger("ADC3 Volume", mute ? 0 : 6);
alsa.setInteger("ADC1 Volume", mute ? 0 : 8);
alsa.setInteger("ADC3 Volume", mute ? 0 : 8);
alsa.setInteger("TX1 Digital Volume", mute ? 0 : 88);
alsa.setBoolean("VoiceMMode1 Capture Mixer TERT_MI2S_TX", !mute);
runAsyncCmd(mute ? "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ 1 >/dev/null 2>&1"
: "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ 0 >/dev/null 2>&1");
@@ -494,7 +540,15 @@ int callscreen_run_main(int argc, char** argv) {
auto setSpeakerRoute = [&](bool toSpeaker) {
speakerOn = toSpeaker;
long hwVol = std::lround((callVolumePct * 84.0) / 100.0);
long hwVol = pctToHwVol(callVolumePct, toSpeaker);
alsa.setBoolean("PRI_MI2S_RX Voice Mixer VoiceMMode1", true);
alsa.setBoolean("VoiceMMode1 Capture Mixer TERT_MI2S_TX", !micMuted);
alsa.setEnum("CIC1 MUX", 0);
if (!micMuted) {
alsa.setEnum("DEC1 MUX", savedMicMux > 0 ? savedMicMux : 1);
alsa.setInteger("ADC1 Volume", 8);
alsa.setInteger("TX1 Digital Volume", 88);
}
if (toSpeaker) {
alsa.setEnum("EAR_S", 0);
alsa.setEnum("RX1 MIX1 INP1", 0);
@@ -505,9 +559,11 @@ int callscreen_run_main(int argc, char** argv) {
} else {
alsa.setBoolean("SPK DAC Switch", false);
alsa.setEnum("RX3 MIX1 INP1", 0);
alsa.setInteger("RX3 Digital Volume", 0);
alsa.setEnum("RX1 MIX1 INP1", 3);
alsa.setEnum("RDAC2 MUX", 0);
alsa.setEnum("EAR_S", 1);
alsa.setBoolean("RX1 Mute Switch", false);
alsa.setInteger("RX1 Digital Volume", hwVol);
statusNotice = "Audio routed to Earpiece";
}
@@ -516,10 +572,10 @@ int callscreen_run_main(int argc, char** argv) {
auto adjustVolume = [&](int deltaPct) {
callVolumePct = std::clamp(callVolumePct + deltaPct, 5, 100);
long hwVol = std::lround((callVolumePct * 84.0) / 100.0);
alsa.setInteger("RX1 Digital Volume", hwVol);
writeIntFile("/tmp/sxmo_call_vol", callVolumePct);
alsa.setInteger("RX1 Digital Volume", pctToHwVol(callVolumePct, false));
if (speakerOn) {
alsa.setInteger("RX3 Digital Volume", hwVol);
alsa.setInteger("RX3 Digital Volume", pctToHwVol(callVolumePct, true));
}
char cmd[128];
std::snprintf(cmd, sizeof(cmd),
@@ -528,6 +584,24 @@ int callscreen_run_main(int argc, char** argv) {
runAsyncCmd(cmd);
};
auto finishRecordingFile = [&]() {
alsa.setBoolean("MultiMedia2 Mixer TERT_MI2S_TX", false);
finalizeWavHeader(recordFilePath);
struct stat st {};
if (!recordFilePath.empty() && ::stat(recordFilePath.c_str(), &st) == 0 &&
st.st_size > 44) {
int durFromBytes = static_cast<int>((st.st_size - 44 + 8000) / 16000);
int durFromWall = static_cast<int>((nowMs() - recordStartMs + 500) / 1000);
lastRecordedSec = std::max(1, std::max(durFromBytes, durFromWall));
++recordedClipCount;
statusNotice = "SAVED (" + formatTimerSec(lastRecordedSec) + ") " + recordFileName;
statusNoticeUntilMs = nowMs() + 8000;
} else if (!recordFileName.empty()) {
statusNotice = "Recording failed";
statusNoticeUntilMs = nowMs() + 4000;
}
};
auto checkRecordingAlive = [&]() -> bool {
if (recordPid <= 0) {
return false;
@@ -536,7 +610,7 @@ int callscreen_run_main(int argc, char** argv) {
pid_t r = ::waitpid(recordPid, &status, WNOHANG);
if (r == recordPid || (r < 0 && ::kill(recordPid, 0) != 0)) {
recordPid = -1;
finalizeWavHeader(recordFilePath);
finishRecordingFile();
return false;
}
return true;
@@ -565,11 +639,7 @@ int callscreen_run_main(int argc, char** argv) {
}
recordPid = -1;
}
finalizeWavHeader(recordFilePath);
if (!recordFileName.empty()) {
statusNotice = "Saved: " + recordFileName;
statusNoticeUntilMs = nowMs() + 3500;
}
finishRecordingFile();
};
auto startRecording = [&]() -> bool {
@@ -581,18 +651,19 @@ int callscreen_run_main(int argc, char** argv) {
struct tm tmNow {};
localtime_r(&nowSec, &tmNow);
char tsBuf[32];
std::strftime(tsBuf, sizeof(tsBuf), "%Y%m%d_%H%M%S", &tmNow);
std::strftime(tsBuf, sizeof(tsBuf), "%H%M%S", &tmNow);
std::string tag = sanitizeForFilename(
!contactName.empty() ? contactName : (!callNumber.empty() ? callNumber : "voice"));
recordFileName = "call_" + tag + "_" + tsBuf + ".wav";
recordFileName = std::string("rec_") + tsBuf + "_" + tag + ".wav";
recordFilePath = dir + "/" + recordFileName;
// Ensure MultiMedia2 capture mixer and primary mic path are active for PipeWire recording
// Enable MultiMedia2 capture mixer at 8 kHz mono to match active VoiceMMode1 AFE port
alsa.setEnum("CIC1 MUX", 0);
if (!micMuted) {
alsa.setEnum("DEC1 MUX", savedMicMux > 0 ? savedMicMux : 1);
alsa.setInteger("ADC1 Volume", 6);
alsa.setInteger("ADC1 Volume", 8);
alsa.setInteger("TX1 Digital Volume", 88);
}
alsa.setBoolean("MultiMedia2 Mixer TERT_MI2S_TX", true);
@@ -602,12 +673,6 @@ int callscreen_run_main(int argc, char** argv) {
}
if (pid == 0) {
::setsid();
std::string runtimeDir = "/run/user/" + std::to_string(::getuid());
const char* xdg = std::getenv("XDG_RUNTIME_DIR");
struct stat stRuntime {};
if (!xdg || !*xdg || ::stat(xdg, &stRuntime) != 0) {
::setenv("XDG_RUNTIME_DIR", runtimeDir.c_str(), 1);
}
int nullfd = ::open("/dev/null", O_RDWR);
if (nullfd >= 0) {
::dup2(nullfd, STDIN_FILENO);
@@ -617,19 +682,17 @@ int callscreen_run_main(int argc, char** argv) {
::close(nullfd);
}
}
execlp("pw-record", "pw-record", "--format", "s16", "--rate", "16000",
"--channels", "1", recordFilePath.c_str(),
execlp("arecord", "arecord", "-q", "-D", "hw:0,1", "-f", "S16_LE",
"-r", "8000", "-c", "1", recordFilePath.c_str(),
static_cast<char*>(nullptr));
execlp("parecord", "parecord", "--rate=16000", "--channels=1",
"--file-format=wav", recordFilePath.c_str(), static_cast<char*>(nullptr));
execlp("arecord", "arecord", "-f", "S16_LE", "-r", "16000", "-c", "1",
execlp("arecord", "arecord", "-q", "-f", "S16_LE", "-r", "8000", "-c", "1",
recordFilePath.c_str(), static_cast<char*>(nullptr));
_exit(1);
}
recordPid = pid;
recordStartMs = nowMs();
statusNotice = "Recording to " + recordFileName;
statusNoticeUntilMs = nowMs() + 2500;
statusNotice = "REC -> " + recordFileName;
statusNoticeUntilMs = nowMs() + 3000;
return true;
};
@@ -804,9 +867,17 @@ int callscreen_run_main(int argc, char** argv) {
drawPango(layoutSmall, &colLight, 8, 88, 26,
"[Call] Answer [*] Mute [End] Reject", 224);
} else if (callState == CallState::Ended) {
drawPango(layoutSmall, &colMuted, 8, 88, 26,
recordFileName.empty() ? "Call finished" : ("Saved: " + recordFileName),
224);
if (recordedClipCount > 0 && !recordFileName.empty()) {
std::string savedStr = "SAVED (" + formatTimerSec(lastRecordedSec) + ") " +
recordFileName;
drawPango(layoutSmall, &colGreen, 8, 88, 26, savedStr, 224);
} else {
drawPango(layoutSmall, &colMuted, 8, 88, 26, "Call finished", 224);
}
} else if (recordedClipCount > 0 && !recordFileName.empty()) {
std::string savedStr = "SAVED (" + formatTimerSec(lastRecordedSec) + ") " +
recordFileName;
drawPango(layoutSmall, &colGreen, 8, 88, 26, savedStr, 224);
} else {
drawPango(layoutSmall, &colMuted, 8, 88, 26,
"[1] Mic [2] Rec [3] Route [0] DTMF", 224);
@@ -883,16 +954,21 @@ int callscreen_run_main(int argc, char** argv) {
"[1] Microphone", std::max(80, kWinWidth - vw - 48));
drawPango(layoutMain, icCol, kWinWidth - vw - 8, ry, rowH, valStr);
} else if (i == 1) {
XftColor* icCol = isRec ? &colRed : &colMuted;
std::string valStr = isRec
? ("REC " + formatTimerSec(static_cast<int>((now - recordStartMs) / 1000)))
: "OFF";
XftColor* icCol = isRec ? &colRed
: (recordedClipCount > 0 ? &colGreen : &colMuted);
std::string valStr;
if (isRec) {
valStr = "REC " + formatTimerSec(static_cast<int>((now - recordStartMs) / 1000));
} else if (recordedClipCount > 0) {
valStr = "SAVED " + formatTimerSec(lastRecordedSec);
} else {
valStr = "OFF";
}
int vw = pangoWidth(layoutMain, valStr);
drawPango(layoutIcon, icCol, 10, ry, rowH, kIconRecord);
drawPango(layoutMain, isSel ? &colWhite : &colLight, 34, ry, rowH,
"[2] Record", std::max(80, kWinWidth - vw - 44));
drawPango(layoutMain, isRec ? &colRed : &colMuted,
kWinWidth - vw - 8, ry, rowH, valStr);
"[2] Record", std::max(72, kWinWidth - vw - 44));
drawPango(layoutMain, icCol, kWinWidth - vw - 8, ry, rowH, valStr);
} else if (i == 2) {
const char* ic = speakerOn ? kIconSpeaker : kIconEarpiece;
XftColor* icCol = speakerOn ? &colYellow : &colBlue;
@@ -1242,9 +1318,13 @@ int callscreen_run_main(int argc, char** argv) {
}
}
if (foundState == "active" && callState != CallState::Active) {
if (callState != CallState::Outgoing) {
speakerOn = false;
}
callState = CallState::Active;
callStartMs = nowMs();
ringMuted = false;
setSpeakerRoute(speakerOn);
writeActiveStateFiles();
render();
} else if (foundState == "ringing-in" && callState == CallState::Idle) {
@@ -1359,10 +1439,15 @@ int callscreen_run_main(int argc, char** argv) {
callId.clear();
callNumber.clear();
contactName.clear();
recordFileName.clear();
recordFilePath.clear();
lastRecordedSec = 0;
recordedClipCount = 0;
updateCallerDetails(arg1, arg2);
callState = CallState::Incoming;
callStartMs = nowMs();
ringMuted = false;
speakerOn = false;
dtmfMode = false;
dtmfSent.clear();
selectedRow = 0;
@@ -1372,24 +1457,39 @@ int callscreen_run_main(int argc, char** argv) {
callId.clear();
callNumber.clear();
contactName.clear();
recordFileName.clear();
recordFilePath.clear();
lastRecordedSec = 0;
recordedClipCount = 0;
updateCallerDetails(arg1, arg2);
callState = CallState::Outgoing;
callStartMs = nowMs();
ringMuted = false;
speakerOn = false;
dtmfMode = false;
dtmfSent.clear();
selectedRow = 0;
setSpeakerRoute(false);
statusNotice.clear();
statusNoticeUntilMs = 0;
writeActiveStateFiles();
showWindow();
} else if (verb == "ACTIVE") {
bool wasActive = (callState == CallState::Active);
bool wasOutgoing = (callState == CallState::Outgoing);
updateCallerDetails(arg1, arg2);
callState = CallState::Active;
if (!wasActive) {
callStartMs = nowMs();
if (!wasOutgoing) {
speakerOn = false;
}
}
ringMuted = false;
selectedRow = 0;
setSpeakerRoute(speakerOn);
statusNotice.clear();
statusNoticeUntilMs = 0;
writeActiveStateFiles();
showWindow();
} else if (verb == "OPEN") {
+9 -1
View File
@@ -1003,7 +1003,9 @@ int runNetworksApp(int argc, char** argv) {
}
std::string cmd = "nmcli connection add type gsm con-name " +
shellQuote(cleanApn) + " ifname '*' apn " +
shellQuote(cleanApn) + " connection.autoconnect yes";
shellQuote(cleanApn) +
" ipv4.method auto ipv6.method ignore"
" connection.autoconnect yes";
if (!trimStr(apnUser).empty()) {
cmd += " gsm.username " + shellQuote(trimStr(apnUser));
}
@@ -1148,9 +1150,15 @@ int runNetworksApp(int argc, char** argv) {
bool turnOn = !cellInfo.gsmActive;
setBanner(turnOn ? "Enabling Mobile Data..." : "Disabling Mobile Data...");
render();
if (turnOn) {
runCaptureCmd("nmcli connection modify " +
shellQuote(cellInfo.gsmConName) +
" ipv4.method auto ipv6.method ignore >/dev/null 2>&1");
}
runCaptureCmd("nmcli connection " + std::string(turnOn ? "up " : "down ") +
shellQuote(cellInfo.gsmConName) + " >/dev/null 2>&1");
refreshCurrentTab(false);
runAsyncCmd("sxmo_hook_statusbar.sh modem >/dev/null 2>&1");
}
} else if (cellRowSel == 1) {
apnName = cellInfo.apn;
+33 -15
View File
@@ -46,7 +46,7 @@ struct RingSettings {
int ringVol = 100;
int ringTimeSec = 20;
int ringLoops = 10;
int vibrateStrength = 1;
int vibrateStrength = 2;
};
constexpr int kRingTimeSteps[] = {10, 15, 20, 25, 30, 45, 60};
@@ -164,6 +164,10 @@ std::string loadActiveRingtonePath() {
}
const char* envRing = std::getenv("SXMO_RINGTONE");
if (envRing && *envRing) return envRing;
struct stat stDef {};
if (::stat("/usr/share/sxmo/ringtones/Banana_Chime.wav", &stDef) == 0) {
return "/usr/share/sxmo/ringtones/Banana_Chime.wav";
}
return "/usr/share/sxmo/ring.ogg";
}
@@ -386,15 +390,22 @@ int runRingtonesApp(int argc, char** argv) {
settings = loadRingSettings();
int rvol = std::clamp(settings.ringVol, 0, 100);
int rx3 = (rvol <= 0) ? 0 : (60 + rvol * 34 / 100);
int mpvVol = rvol * 130 / 100;
int mpvVol = rvol;
std::string volArg = "--volume=" + std::to_string(mpvVol);
runAsyncCmd("amixer -c 0 cset \"name='EAR_S'\" 0 >/dev/null 2>&1; "
"amixer -c 0 cset \"name='RX1 MIX1 INP1'\" 0 >/dev/null 2>&1; "
"amixer -c 0 cset \"name='PRI_MI2S_RX Audio Mixer MultiMedia1'\" 1 >/dev/null 2>&1; "
"amixer -c 0 cset \"name='SPK DAC Switch'\" 1 >/dev/null 2>&1; "
"amixer -c 0 cset \"name='RX3 MIX1 INP1'\" 3 >/dev/null 2>&1; "
"amixer -c 0 cset \"name='RX3 Digital Volume'\" " +
std::to_string(rx3) + " >/dev/null 2>&1; "
"wpctl set-mute @DEFAULT_AUDIO_SINK@ 0 >/dev/null 2>&1; "
"wpctl set-volume @DEFAULT_AUDIO_SINK@ 100% >/dev/null 2>&1");
"wpctl set-volume @DEFAULT_AUDIO_SINK@ " +
std::to_string(rvol) + "% >/dev/null 2>&1; "
"(sleep 0.25; amixer -c 0 cset \"name='RX3 Digital Volume'\" " +
std::to_string(rx3) + " >/dev/null 2>&1; "
"wpctl set-volume @DEFAULT_AUDIO_SINK@ " +
std::to_string(rvol) + "% >/dev/null 2>&1) &");
pid_t pid = ::fork();
if (pid == 0) {
std::string runtimeDir = "/run/user/" + std::to_string(::getuid());
@@ -410,7 +421,8 @@ int runRingtonesApp(int argc, char** argv) {
::dup2(devNull, STDERR_FILENO);
if (devNull > 2) ::close(devNull);
}
::execlp("mpv", "mpv", "--no-resume-playback", "--quiet",
::execlp("mpv", "mpv", "--no-config", "--no-terminal",
"--no-resume-playback", "--quiet", "--vo=null",
"--no-video", "--ao=pipewire,pulse,alsa",
"--audio-channels=stereo", "--audio-samplerate=48000",
"--volume-max=150", volArg.c_str(),
@@ -564,6 +576,8 @@ int runRingtonesApp(int argc, char** argv) {
"Vibrate Only",
"Silent",
};
const char* vibLabels[3] = {"1 (Light)", "2 (Medium)", "3 (Strong)"};
int vIdx = std::clamp(settings.vibrateStrength, 1, 3) - 1;
std::string rows[6][2] = {
{"Ringer Mode",
"< " + std::string(modeLabels[settings.ringerMode % 4]) + " >"},
@@ -574,7 +588,7 @@ int runRingtonesApp(int argc, char** argv) {
{"Ring Loop Count",
"< " + std::to_string(settings.ringLoops) + " loops >"},
{"Vibrate Strength",
"< Level " + std::to_string(settings.vibrateStrength) + " >"},
"< Level " + std::string(vibLabels[vIdx]) + " >"},
{"Test Vibration", "Press OK to vibrate"},
};
@@ -665,6 +679,13 @@ int runRingtonesApp(int argc, char** argv) {
} else if (settingsRow == 1) {
settings.ringVol = std::clamp(settings.ringVol + delta * 5, 0, 100);
saveRingSettings(settings);
if (previewPid > 0) {
int rx3 = (settings.ringVol <= 0) ? 0 : (60 + settings.ringVol * 34 / 100);
runAsyncCmd("amixer -c 0 cset \"name='RX3 Digital Volume'\" " +
std::to_string(rx3) + " >/dev/null 2>&1; "
"wpctl set-volume @DEFAULT_AUDIO_SINK@ " +
std::to_string(settings.ringVol) + "% >/dev/null 2>&1");
}
setBanner("Ring volume: " + std::to_string(settings.ringVol) + "%");
} else if (settingsRow == 2) {
int curIdx = 2;
@@ -692,17 +713,14 @@ int runRingtonesApp(int argc, char** argv) {
settings.vibrateStrength =
((settings.vibrateStrength - 1 + 3 + delta) % 3) + 1;
saveRingSettings(settings);
runAsyncCmd("SXMO_VIBRATE_FORCE=1 sxmo_vibrate 450 " +
std::to_string(settings.vibrateStrength) + " >/dev/null 2>&1");
setBanner("Vibration level: " + std::to_string(settings.vibrateStrength));
} else if (settingsRow == 5) {
int mag = (settings.vibrateStrength == 1)
? 8000
: (settings.vibrateStrength == 3 ? 65535 : 32000);
runAsyncCmd(
"for d in /dev/input/by-path/platform-vibrator-event "
"/dev/input/by-path/*vibrator*-event; do "
"[ -e \"$d\" ] && export SXMO_VIBRATE_DEV=\"$d\" && break; done; "
"sxmo_vibrate 600 " +
std::to_string(mag) + " >/dev/null 2>&1");
setBanner("Testing vibration...");
runAsyncCmd("SXMO_VIBRATE_FORCE=1 sxmo_vibrate 800 " +
std::to_string(settings.vibrateStrength) + " >/dev/null 2>&1");
setBanner("Testing vibration (Level " +
std::to_string(settings.vibrateStrength) + ")...");
}
};
+107 -26
View File
@@ -2,6 +2,7 @@
#include <fcntl.h>
#include <glob.h>
#include <linux/input.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -9,17 +10,42 @@
#include <time.h>
#include <unistd.h>
static int read_configured_level(void) {
char path[512];
static volatile sig_atomic_t g_stop = 0;
static void handle_signal(int sig) {
(void)sig;
g_stop = 1;
}
static void get_sxmo_config_dir(char* out, size_t out_sz) {
const char* cfg = getenv("XDG_CONFIG_HOME");
const char* home = getenv("HOME");
if (cfg && *cfg) {
snprintf(path, sizeof(path), "%s/sxmo/ringsettings", cfg);
snprintf(out, out_sz, "%s/sxmo", cfg);
} else if (home && *home) {
snprintf(path, sizeof(path), "%s/.config/sxmo/ringsettings", home);
snprintf(out, out_sz, "%s/.config/sxmo", home);
} else {
snprintf(path, sizeof(path), "/home/user/.config/sxmo/ringsettings");
snprintf(out, out_sz, "/home/user/.config/sxmo");
}
}
static int is_vibrate_muted(void) {
const char* force = getenv("SXMO_VIBRATE_FORCE");
if (force && *force == '1') {
return 0;
}
char dir[448];
char path[512];
get_sxmo_config_dir(dir, sizeof(dir));
snprintf(path, sizeof(path), "%s/.novibrate", dir);
return access(path, F_OK) == 0;
}
static int read_configured_level(void) {
char dir[448];
char path[512];
get_sxmo_config_dir(dir, sizeof(dir));
snprintf(path, sizeof(path), "%s/ringsettings", dir);
FILE* fp = fopen(path, "r");
if (!fp) return 0;
@@ -32,24 +58,45 @@ static int read_configured_level(void) {
if (strncmp(p, "SXMO_VIBRATE_STRENGTH=", 22) == 0) {
int v = atoi(p + 22);
if (v >= 1 && v <= 3) level = v;
else if (v > 3 && v <= 16000) level = 1;
else if (v > 16000 && v <= 45000) level = 2;
else if (v > 45000) level = 3;
}
}
fclose(fp);
return level;
}
static int resolve_magnitude(int raw_arg, int has_arg) {
static int resolve_level_and_magnitude(int raw_arg, int has_arg, int* level_out) {
int val = raw_arg;
if (!has_arg || (val == 1 && !getenv("SXMO_VIBRATE_STRENGTH"))) {
const char* env_str = getenv("SXMO_VIBRATE_STRENGTH");
if (!has_arg || (val == 1 && (!env_str || !*env_str))) {
int cfg = read_configured_level();
if (cfg >= 1 && cfg <= 3) val = cfg;
if (cfg >= 1 && cfg <= 3) {
val = cfg;
} else if (env_str && *env_str) {
int ev = atoi(env_str);
if (ev >= 1) val = ev;
} else if (!has_arg) {
val = 2;
}
}
if (val <= 1) return 8000;
if (val == 2) return 32000;
if (val == 3) return 65535;
if (val < 256) return val * 257;
if (val > 65535) return 65535;
return val;
int level = 2;
int mag = 32000;
if (val <= 1 || (val >= 256 && val <= 16000)) {
level = 1;
mag = 14000;
} else if (val == 2 || (val > 16000 && val <= 45000)) {
level = 2;
mag = 32000;
} else {
level = 3;
mag = 65535;
}
if (level_out) *level_out = level;
return mag;
}
static int open_vibrator_device(void) {
@@ -77,6 +124,15 @@ static int open_vibrator_device(void) {
return -1;
}
static void sleep_ms_interruptible(long ms) {
if (ms <= 0 || g_stop) return;
struct timespec req;
req.tv_sec = ms / 1000;
req.tv_nsec = (ms % 1000) * 1000000L;
while (!g_stop && nanosleep(&req, &req) == -1 && errno == EINTR) {
}
}
int main(int argc, char** argv) {
if (argc < 2 || argc > 3) {
fprintf(stderr, "Usage: sxmo_vibrate duration_ms [strength_number]\n");
@@ -91,6 +147,10 @@ int main(int argc, char** argv) {
return 1;
}
if (is_vibrate_muted()) {
return 0;
}
int raw_strength = 2;
int has_strength = 0;
if (argc == 3) {
@@ -103,7 +163,16 @@ int main(int argc, char** argv) {
}
}
int magnitude = resolve_magnitude(raw_strength, has_strength);
int level = 2;
int magnitude = resolve_level_and_magnitude(raw_strength, has_strength, &level);
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handle_signal;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGHUP, &sa, NULL);
int fd = open_vibrator_device();
if (fd < 0) {
@@ -128,18 +197,30 @@ int main(int argc, char** argv) {
memset(&play, 0, sizeof(play));
play.type = EV_FF;
play.code = (unsigned short)effect.id;
play.value = 3;
if (write(fd, &play, sizeof(play)) < 0) {
ioctl(fd, EVIOCRMFF, effect.id);
close(fd);
return 1;
}
struct timespec req;
req.tv_sec = duration_ms / 1000;
req.tv_nsec = (duration_ms % 1000) * 1000000L;
while (nanosleep(&req, &req) == -1 && errno == EINTR) {
if (level >= 3 || duration_ms <= 90) {
long actual_ms = (level == 1 && duration_ms > 55) ? 55 : duration_ms;
play.value = 1;
if (write(fd, &play, sizeof(play)) >= 0) {
sleep_ms_interruptible(actual_ms);
}
} else {
long on_ms = (level == 1) ? 75 : 160;
long off_ms = (level == 1) ? 125 : 80;
long rem = duration_ms;
while (rem > 0 && !g_stop) {
long cur_on = (rem < on_ms) ? rem : on_ms;
play.value = 1;
if (write(fd, &play, sizeof(play)) < 0) break;
sleep_ms_interruptible(cur_on);
rem -= cur_on;
play.value = 0;
(void)write(fd, &play, sizeof(play));
if (rem <= 0 || g_stop) break;
long cur_off = (rem < off_ms) ? rem : off_ms;
sleep_ms_interruptible(cur_off);
rem -= cur_off;
}
}
play.value = 0;
+63 -16
View File
@@ -43,7 +43,7 @@ constexpr int kWinX = 0;
constexpr int kWinY = kTopBarHeight;
constexpr int kWinWidth = kScreenWidth;
constexpr int kWinHeight = kScreenHeight - kTopBarHeight; // 296px flush below 24px dwm bar
constexpr int kRowCount = 9;
constexpr int kRowCount = 10;
// Nerd Font PUA icons matching sxmo_hook_icons.sh & dwm ("Fira Code, DejaVuSansM Nerd Font")
constexpr const char* kIconSpeaker = "\xef\x80\xa8"; // U+F028 speaker
@@ -242,11 +242,12 @@ enum class ControlId {
EarpieceVol = 1,
RingVol = 2,
RingerMode = 3,
Flashlight = 4,
SoundMute = 5,
MicMute = 6,
ScreenBright = 7,
KbdBright = 8,
VibrateStrength = 4,
Flashlight = 5,
SoundMute = 6,
MicMute = 7,
ScreenBright = 8,
KbdBright = 9,
};
struct ControlItem {
@@ -300,15 +301,15 @@ class PhoneController {
items_.push_back({ControlId::SpeakerVol, kIconSpeaker, kIconSoundOff,
"Speaker", true, 0, 100, 5, spkPct});
// 2. Earpiece volume (0..100%, mapped to ALSA RX1 Digital Volume 58..90)
long rx1 = alsa_.getInteger("RX1 Digital Volume", 84);
int earPct = 100;
// 2. Earpiece volume (0..100%, mapped to ALSA RX1 Digital Volume 68..92)
long rx1 = alsa_.getInteger("RX1 Digital Volume", 88);
int earPct = 85;
if (rx1 <= 0) {
earPct = 0;
} else if (rx1 < 84) {
earPct = std::clamp(static_cast<int>(std::lround((rx1 * 100.0) / 84.0)), 5, 100);
} else if (rx1 >= 68) {
earPct = std::clamp(static_cast<int>(std::lround(((rx1 - 68) * 100.0) / 24.0)), 5, 100);
}
earPct = (earPct / 5) * 5;
earPct = ((earPct + 2) / 5) * 5;
items_.push_back({ControlId::EarpieceVol, kIconEarpiece, kIconEarpiece,
"Earpiece", true, 0, 100, 5, earPct});
@@ -322,6 +323,11 @@ class PhoneController {
items_.push_back({ControlId::RingerMode, kIconBell, kIconBellSlash,
"Ringer", false, 0, 3, 1, ringerVal});
// 5. Vibration Strength (1=Light, 2=Medium, 3=Strong)
int vibVal = readVibrateStrength();
items_.push_back({ControlId::VibrateStrength, kIconBell, kIconBell,
"Vibration", false, 1, 3, 1, vibVal});
// 5. Flashlight on/off (toggle)
int torchCur = readIntFile(torchDir_ + "/brightness", 0);
items_.push_back({ControlId::Flashlight, kIconFlashlight, kIconFlashlight,
@@ -373,6 +379,8 @@ class PhoneController {
it.value = std::clamp(it.value + dir * it.stepVal, it.minVal, it.maxVal);
} else if (it.id == ControlId::RingerMode) {
it.value = (it.value + dir + 4) % 4;
} else if (it.id == ControlId::VibrateStrength) {
it.value = ((it.value - 1 + dir + 3) % 3) + 1;
} else {
it.value = it.value ? 0 : 1;
}
@@ -387,6 +395,9 @@ class PhoneController {
if (it.id == ControlId::RingerMode) {
it.value = (it.value + 1) % 4;
apply(it);
} else if (it.id == ControlId::VibrateStrength) {
it.value = (it.value % 3) + 1;
apply(it);
} else if (!it.isSlider) {
it.value = it.value ? 0 : 1;
apply(it);
@@ -432,13 +443,30 @@ class PhoneController {
return 100;
}
static void writeRingVolume(int ringVol) {
static int readVibrateStrength() {
std::ifstream ifs(getSxmoCfgDir() + "/ringsettings");
std::string line;
while (std::getline(ifs, line)) {
auto eq = line.find('=');
if (eq == std::string::npos) continue;
std::string k = line.substr(0, eq);
std::string v = line.substr(eq + 1);
if (k == "SXMO_VIBRATE_STRENGTH") {
int val = std::atoi(v.c_str());
if (val >= 1 && val <= 3) return val;
}
}
return 2;
}
static void writeRingAndVibSettings(int newRingVol, int newVibStrength) {
std::string cfg = getSxmoCfgDir();
::mkdir(cfg.c_str(), 0755);
std::string path = cfg + "/ringsettings";
int ringTime = 20;
int ringLoops = 10;
int vibStrength = 2;
int ringVol = 100;
{
std::ifstream ifs(path);
std::string line;
@@ -450,14 +478,17 @@ class PhoneController {
if (k == "SXMO_RINGTIME" && val >= 5 && val <= 120) ringTime = val;
else if (k == "SXMO_RINGNUMBER" && val >= 1 && val <= 100) ringLoops = val;
else if (k == "SXMO_VIBRATE_STRENGTH" && val >= 1 && val <= 3) vibStrength = val;
else if (k == "SXMO_RINGVOL" && val >= 0 && val <= 100) ringVol = val;
}
}
if (newRingVol >= 0) ringVol = std::clamp(newRingVol, 0, 100);
if (newVibStrength >= 1 && newVibStrength <= 3) vibStrength = newVibStrength;
std::ofstream ofs(path, std::ios::trunc);
if (ofs) {
ofs << "SXMO_RINGTIME=" << ringTime << "\n";
ofs << "SXMO_RINGNUMBER=" << ringLoops << "\n";
ofs << "SXMO_VIBRATE_STRENGTH=" << vibStrength << "\n";
ofs << "SXMO_RINGVOL=" << std::clamp(ringVol, 0, 100) << "\n";
ofs << "SXMO_RINGVOL=" << ringVol << "\n";
}
}
@@ -484,12 +515,21 @@ class PhoneController {
break;
}
case ControlId::EarpieceVol: {
long hw = (it.value <= 0) ? 0 : (58 + std::lround((it.value * 32.0) / 100.0));
long hw = (it.value <= 0) ? 0 : (68 + std::lround((it.value * 24.0) / 100.0));
alsa_.setInteger("RX1 Digital Volume", hw);
break;
}
case ControlId::RingVol: {
writeRingVolume(it.value);
writeRingAndVibSettings(it.value, -1);
break;
}
case ControlId::VibrateStrength: {
writeRingAndVibSettings(-1, it.value);
char cmd[96];
std::snprintf(cmd, sizeof(cmd),
"SXMO_VIBRATE_FORCE=1 sxmo_vibrate 400 %d >/dev/null 2>&1",
it.value);
runAsyncCmd(cmd);
break;
}
case ControlId::Flashlight: {
@@ -792,6 +832,13 @@ static int xdrawer_run_main(int argc, char** argv) {
XftColor* rCol = (it.value == 0) ? (isSel ? &colYellow : &colGreen)
: ((it.value == 1) ? &colYellow : &colMuted);
drawPango(layoutSmall, rCol, kWinWidth - stW - 6, ry, rowH, stStr);
} else if (it.id == ControlId::VibrateStrength) {
const char* vModes[3] = {"Light", "Medium", "Strong"};
int vIdx = std::clamp(it.value, 1, 3) - 1;
std::string stStr = std::string("< ") + vModes[vIdx] + " >";
int stW = pangoWidth(layoutSmall, stStr);
XftColor* vCol = isSel ? &colYellow : &colGreen;
drawPango(layoutSmall, vCol, kWinWidth - stW - 6, ry, rowH, stStr);
} else {
bool on = (it.value != 0);
std::string stStr = on ? "ON" : "OFF";