Fix mobile data static IP bearer, call recording, earpiece audio, and ringtone/vibration
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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.
@@ -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") {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user