From 2650f0662375746c160ddde61c27bce4d361b952 Mon Sep 17 00:00:00 2001 From: Felix MIL Date: Fri, 5 Jun 2026 09:17:58 +0200 Subject: [PATCH 1/9] Make Brick Fn backlight action use spruce's own backlight control - com.trimui.switch.backlight.sh: - source helperFunctions and cycle via spruce set_backlight / current_backlight - cycle levels 1, 4, 7, 10 then wrap, keeping hardware, stored value and UI slider in sync - drop the stock /tmp/system/set_brightness path that spruce does not honor --- App/fn_editor/com.trimui.switch.backlight.sh | 30 ++++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/App/fn_editor/com.trimui.switch.backlight.sh b/App/fn_editor/com.trimui.switch.backlight.sh index 1ccc07283..d5508f734 100644 --- a/App/fn_editor/com.trimui.switch.backlight.sh +++ b/App/fn_editor/com.trimui.switch.backlight.sh @@ -1,18 +1,24 @@ #!/bin/sh -LCD_BL=`/usr/trimui/bin/systemval brightness` -LCD_BL_SET=$LCD_BL +# Cycle the LCD backlight through a few levels on each Fn key press. +# Uses spruce's own set_backlight so the hardware, the stored value, and the +# in-UI brightness slider all stay in sync (the stock /tmp/system/set_brightness +# path is not honored under spruce and desyncs the slider). -echo "get brightness:"$LCD_BL +. /mnt/SDCARD/spruce/scripts/helperFunctions.sh -if test $LCD_BL -lt 1; then - LCD_BL_SET=2 -elif test $LCD_BL -lt 3; then - LCD_BL_SET=5 -elif test $LCD_BL -lt 7; then - LCD_BL_SET=10 +CURRENT=$(current_backlight) +echo "get brightness:$CURRENT" + +# Cycle 1 -> 4 -> 7 -> 10 -> 1 (spruce clamps to 1..10) +if [ "$CURRENT" -lt 4 ]; then + NEXT=4 +elif [ "$CURRENT" -lt 7 ]; then + NEXT=7 +elif [ "$CURRENT" -lt 10 ]; then + NEXT=10 else - LCD_BL_SET=0 + NEXT=1 fi -echo "set brightness:"$LCD_BL_SET -echo -n $LCD_BL_SET > /tmp/system/set_brightness +echo "set brightness:$NEXT" +set_backlight "$NEXT" From b5c7a224fd0382cc18b516a57fa88e969376ae88 Mon Sep 17 00:00:00 2001 From: Felix MIL Date: Fri, 5 Jun 2026 10:19:11 +0200 Subject: [PATCH 2/9] Sync spruce volume value when Silent Mode toggles - com.trimui.silent.sh: - source helperFunctions and drop volume to 0 via spruce set_volume on enter, so the in-UI volume value reflects the mute - save the prior volume and restore it via set_volume on exit - keep the speaker hard-mute as a backstop --- App/fn_editor/com.trimui.silent.sh | 32 ++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/App/fn_editor/com.trimui.silent.sh b/App/fn_editor/com.trimui.silent.sh index 959d56634..a07070ff2 100644 --- a/App/fn_editor/com.trimui.silent.sh +++ b/App/fn_editor/com.trimui.silent.sh @@ -1,17 +1,41 @@ #!/bin/sh +# Silent Mode: hard-mute the speaker and reflect it in spruce's volume value so +# the in-UI volume reading matches. On exit, restore the volume that was set +# before entering silent. +# +# Without the set_volume calls the speaker mutes but spruce's stored vol (and the +# settings slider) stays at its old value, so the UI looks wrong while muted. + +. /mnt/SDCARD/spruce/scripts/helperFunctions.sh + +SAVED_VOL_FILE=/tmp/system/silent_saved_vol + echo "============= scene silent ============" mkdir -p /tmp/system/ + case "$1" in -1 ) +1 ) echo "Enter silent" - echo 1 > /sys/class/speaker/mute + # Remember the current volume so we can restore it on exit + cur="$(get_volume_level)" + [ -z "$cur" ] && cur=0 + echo "$cur" > "$SAVED_VOL_FILE" + # Drop spruce's volume to 0 (updates SYSTEM_JSON .vol -> UI reflects it) + set_volume 0 + # Belt and suspenders: cut the speaker amp outright + echo 1 > /sys/class/speaker/mute touch /tmp/system/muted ;; 0 ) echo "Exit silent" - echo 0 > /sys/class/speaker/mute + echo 0 > /sys/class/speaker/mute rm -f /tmp/system/muted - ;; + # Restore the volume we had before entering silent + saved="$(cat "$SAVED_VOL_FILE" 2>/dev/null)" + [ -z "$saved" ] && saved=0 + set_volume "$saved" + rm -f "$SAVED_VOL_FILE" + ;; *) ;; esac From b37621c55218c8963ba2dc9c360a945f26f533e1 Mon Sep 17 00:00:00 2001 From: Felix MIL Date: Fri, 5 Jun 2026 11:31:17 +0200 Subject: [PATCH 3/9] Make Brick Fn Quiet Mode lower spruce's own volume - App/fn_editor/com.trimui.quiet.sh: - source spruce helperFunctions for set_volume / get_volume_level - on enter, save the current volume then set spruce volume to a low level - on exit, restore the saved volume and clean up the temp file - drop the tinymix mixer pokes that spruce did not read or respect --- App/fn_editor/com.trimui.quiet.sh | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) mode change 100644 => 100755 App/fn_editor/com.trimui.quiet.sh diff --git a/App/fn_editor/com.trimui.quiet.sh b/App/fn_editor/com.trimui.quiet.sh old mode 100644 new mode 100755 index 3265caf9a..d6867dff3 --- a/App/fn_editor/com.trimui.quiet.sh +++ b/App/fn_editor/com.trimui.quiet.sh @@ -1,15 +1,37 @@ #!/bin/sh +# Quiet Mode: drop the volume to a low level through spruce's own set_volume so +# the in-UI volume reading matches, then restore the previous volume on exit. +# +# The stock script poked an ALSA mixer control directly (tinymix set 9 N), which +# spruce neither reads nor respects, so nothing audible changed and the settings +# slider stayed wrong. Going through set_volume updates SYSTEM_JSON .vol too. + +. /mnt/SDCARD/spruce/scripts/helperFunctions.sh + +QUIET_VOL=4 # spruce volume scale is 0-20; ~20% is low but audible +SAVED_VOL_FILE=/tmp/system/quiet_saved_vol + echo "============= scene quiet ============" +mkdir -p /tmp/system/ case "$1" in -1 ) +1 ) echo "Enter quiet" - tinymix set 9 4 + # Remember the current volume so we can restore it on exit + cur="$(get_volume_level)" + [ -z "$cur" ] && cur=0 + echo "$cur" > "$SAVED_VOL_FILE" + # Lower spruce's volume (updates SYSTEM_JSON .vol -> UI reflects it) + set_volume "$QUIET_VOL" ;; 0 ) echo "Exit quiet" - tinymix set 9 1 - ;; + # Restore the volume we had before entering quiet + saved="$(cat "$SAVED_VOL_FILE" 2>/dev/null)" + [ -z "$saved" ] && saved=0 + set_volume "$saved" + rm -f "$SAVED_VOL_FILE" + ;; *) ;; esac From a2d67dbf0895d9296a4373fb65df2df27fa6efba Mon Sep 17 00:00:00 2001 From: Felix MIL Date: Fri, 5 Jun 2026 11:48:17 +0200 Subject: [PATCH 4/9] Suppress the stock volume OSD when the switch toggles Silent or Quiet - spruce/scripts/platform/device_functions/trimui_a133p.sh: - add an optional SHOW_OSD third arg to set_volume (defaults to true) - only show the stock volume popup when SHOW_OSD is true - App/fn_editor/com.trimui.silent.sh: - pass "true false" to set_volume so the config syncs without the popup - App/fn_editor/com.trimui.quiet.sh: - pass "true false" to set_volume so the config syncs without the popup --- App/fn_editor/com.trimui.quiet.sh | 10 ++++++---- App/fn_editor/com.trimui.silent.sh | 10 ++++++---- .../scripts/platform/device_functions/trimui_a133p.sh | 3 ++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/App/fn_editor/com.trimui.quiet.sh b/App/fn_editor/com.trimui.quiet.sh index d6867dff3..efd8645ed 100755 --- a/App/fn_editor/com.trimui.quiet.sh +++ b/App/fn_editor/com.trimui.quiet.sh @@ -21,15 +21,17 @@ case "$1" in cur="$(get_volume_level)" [ -z "$cur" ] && cur=0 echo "$cur" > "$SAVED_VOL_FILE" - # Lower spruce's volume (updates SYSTEM_JSON .vol -> UI reflects it) - set_volume "$QUIET_VOL" + # Lower spruce's volume (updates SYSTEM_JSON .vol -> UI reflects it). + # Pass "true false" so the config is saved but the stock volume OSD popup + # is not shown; the switch already shows its own toast. + set_volume "$QUIET_VOL" true false ;; 0 ) echo "Exit quiet" - # Restore the volume we had before entering quiet + # Restore the volume we had before entering quiet (no OSD popup) saved="$(cat "$SAVED_VOL_FILE" 2>/dev/null)" [ -z "$saved" ] && saved=0 - set_volume "$saved" + set_volume "$saved" true false rm -f "$SAVED_VOL_FILE" ;; *) diff --git a/App/fn_editor/com.trimui.silent.sh b/App/fn_editor/com.trimui.silent.sh index a07070ff2..2d51df743 100644 --- a/App/fn_editor/com.trimui.silent.sh +++ b/App/fn_editor/com.trimui.silent.sh @@ -20,8 +20,10 @@ case "$1" in cur="$(get_volume_level)" [ -z "$cur" ] && cur=0 echo "$cur" > "$SAVED_VOL_FILE" - # Drop spruce's volume to 0 (updates SYSTEM_JSON .vol -> UI reflects it) - set_volume 0 + # Drop spruce's volume to 0 (updates SYSTEM_JSON .vol -> UI reflects it). + # Pass "true false" so the config is saved but the stock volume OSD popup + # is not shown; the switch already shows its own toast. + set_volume 0 true false # Belt and suspenders: cut the speaker amp outright echo 1 > /sys/class/speaker/mute touch /tmp/system/muted @@ -30,10 +32,10 @@ case "$1" in echo "Exit silent" echo 0 > /sys/class/speaker/mute rm -f /tmp/system/muted - # Restore the volume we had before entering silent + # Restore the volume we had before entering silent (no OSD popup) saved="$(cat "$SAVED_VOL_FILE" 2>/dev/null)" [ -z "$saved" ] && saved=0 - set_volume "$saved" + set_volume "$saved" true false rm -f "$SAVED_VOL_FILE" ;; *) diff --git a/spruce/scripts/platform/device_functions/trimui_a133p.sh b/spruce/scripts/platform/device_functions/trimui_a133p.sh index 02ea9f521..f6f98d670 100644 --- a/spruce/scripts/platform/device_functions/trimui_a133p.sh +++ b/spruce/scripts/platform/device_functions/trimui_a133p.sh @@ -72,6 +72,7 @@ get_current_volume() { set_volume() { new_vol="${1:-0}" # default to mute if no value supplied SAVE_TO_CONFIG="${2:-true}" # Optional 2nd arg, defaults to true + SHOW_OSD="${3:-true}" # Optional 3rd arg, defaults to true mkdir -p /tmp/system 2>/dev/null echo "$new_vol" > /tmp/system/set_volume 2>/dev/null if [ "$SAVE_TO_CONFIG" = true ]; then @@ -80,7 +81,7 @@ set_volume() { if [ "$current_volume" -ne "$new_vol" ]; then save_volume_to_config_file "$new_vol" sed "s/\"vol\":[[:space:]]*[0-9]\+/\"vol\": $new_vol/" /mnt/UDISK/system.json > /mnt/UDISK/system.json.tmp && mv /mnt/UDISK/system.json.tmp /mnt/UDISK/system.json - if ! pgrep MainUI >/dev/null; then + if ! pgrep MainUI >/dev/null && [ "$SHOW_OSD" = true ]; then /usr/trimui/osd/show_volume_msg.sh "$new_vol" & fi fi From 7ab86ef7bb2a61fdf2fc2a98f66fd9614868521c Mon Sep 17 00:00:00 2001 From: Felix MIL Date: Fri, 5 Jun 2026 14:14:21 +0200 Subject: [PATCH 5/9] Sync spruce volume with the firmware volume keys on the Brick - spruce/brick/volume_sync_watchdog.sh: - add a watcher that polls /tmp/system/set_volume and mirrors the value into spruce's stored volume via set_volume (config only, OSD suppressed) so the in-UI volume bar tracks the hardware Volume +/- keys, including while held - spruce/scripts/platform/device_functions/trimui_a133p.sh: - launch volume_sync_watchdog.sh from device_init_a133p - spruce/scripts/save_poweroff.sh: - kill volume_sync_watchdog.sh during shutdown alongside the other watchdogs --- spruce/brick/volume_sync_watchdog.sh | 43 +++++++++++++++++++ .../platform/device_functions/trimui_a133p.sh | 3 ++ spruce/scripts/save_poweroff.sh | 1 + 3 files changed, 47 insertions(+) create mode 100755 spruce/brick/volume_sync_watchdog.sh diff --git a/spruce/brick/volume_sync_watchdog.sh b/spruce/brick/volume_sync_watchdog.sh new file mode 100755 index 000000000..b3c658027 --- /dev/null +++ b/spruce/brick/volume_sync_watchdog.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# Mirror the firmware volume value into spruce's stored volume so the in-UI +# volume bar tracks the hardware Volume +/- keys, including while held. +# +# On the Brick the physical volume keys are handled by the stock firmware +# (trimui_inputd/keymon), which adjusts ALSA and writes the new level to +# /tmp/system/set_volume on every key event (autorepeat included). spruce never +# read that file back, so its own .vol (SYSTEM_JSON) only changed when spruce +# itself set the volume. The UI volume bar reads .vol, so holding a volume key +# moved the audio but not the bar (it updated once at most, never ramped). +# +# This watchdog polls /tmp/system/set_volume and, on each change, pushes the +# value through spruce's set_volume so .vol stays in sync and the bar redraws. +# set_volume is called config-only with the OSD suppressed: the firmware already +# draws its own volume OSD for the hardware keys, and set_volume is a no-op when +# the value is unchanged, so re-syncing the same number is harmless. + +. /mnt/SDCARD/spruce/scripts/helperFunctions.sh + +SET_VOLUME_FILE=/tmp/system/set_volume +POLL_INTERVAL=0.15 + +log_message "volume_sync_watchdog.sh: Started up." + +last="" +while true; do + if [ -f "$SET_VOLUME_FILE" ]; then + new_vol=$(cat "$SET_VOLUME_FILE" 2>/dev/null) + case "$new_vol" in + ''|*[!0-9]*) ;; # ignore empty or non-numeric writes + *) + if [ "$new_vol" != "$last" ]; then + if [ "$new_vol" != "$(get_volume_level)" ]; then + # config-only, suppress spruce OSD (firmware shows its own) + set_volume "$new_vol" true false + fi + last="$new_vol" + fi + ;; + esac + fi + sleep "$POLL_INTERVAL" +done diff --git a/spruce/scripts/platform/device_functions/trimui_a133p.sh b/spruce/scripts/platform/device_functions/trimui_a133p.sh index f6f98d670..f101b8491 100644 --- a/spruce/scripts/platform/device_functions/trimui_a133p.sh +++ b/spruce/scripts/platform/device_functions/trimui_a133p.sh @@ -175,6 +175,9 @@ device_init_a133p() { ) & amixer set 'Soft Volume Master' 255 # reset this to max so we're not double attenuating vol with two different mixer controls run_trimui_blobs "trimui_inputd trimui_scened trimui_btmanager hardwareservice musicserver" + # Keep spruce's stored volume in sync with the firmware volume keys so the + # in-UI volume bar tracks the hardware Volume +/- keys (including on hold). + /mnt/SDCARD/spruce/brick/volume_sync_watchdog.sh & } set_event_arg_for_idlemon() { diff --git a/spruce/scripts/save_poweroff.sh b/spruce/scripts/save_poweroff.sh index b6f11d8c3..02eecffa4 100644 --- a/spruce/scripts/save_poweroff.sh +++ b/spruce/scripts/save_poweroff.sh @@ -181,6 +181,7 @@ stop_problematic_scripts() { killall -q -9 idlemon_mm.sh killall -q -9 low_power_warning.sh killall -q -9 theme_watchdog.sh + killall -q -9 volume_sync_watchdog.sh killall -q -9 inotifywait killall -q -9 inotifywatch killall -q -9 getevent From 5157af67ab038a750dffe421bcfc3fddcd10f86c Mon Sep 17 00:00:00 2001 From: Felix MIL Date: Fri, 5 Jun 2026 15:32:01 +0200 Subject: [PATCH 6/9] Restore audio on volume-up while Silent Mode is active - volume_sync_watchdog.sh: - add unmute_if_raised() to clear the speaker amp mute and the muted marker when a non-zero volume arrives - call unmute_if_raised on each observed volume change so volume-up lifts Silent Mode's hard mute without flipping the switch --- spruce/brick/volume_sync_watchdog.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/spruce/brick/volume_sync_watchdog.sh b/spruce/brick/volume_sync_watchdog.sh index b3c658027..9b26ddeb6 100755 --- a/spruce/brick/volume_sync_watchdog.sh +++ b/spruce/brick/volume_sync_watchdog.sh @@ -14,12 +14,32 @@ # set_volume is called config-only with the OSD suppressed: the firmware already # draws its own volume OSD for the hardware keys, and set_volume is a no-op when # the value is unchanged, so re-syncing the same number is harmless. +# +# It also lifts Silent Mode's hard mute on the first volume-up. Silent Mode mutes +# the speaker amp (/sys/class/speaker/mute) and that mute is otherwise only +# cleared by flipping the switch back. So raising the volume bumped ALSA and the +# OSD but produced no sound. When a non-zero volume arrives while the amp is +# muted we clear the mute (and its marker) so volume-up restores audio without +# touching the switch. . /mnt/SDCARD/spruce/scripts/helperFunctions.sh SET_VOLUME_FILE=/tmp/system/set_volume +SPEAKER_MUTE_FILE=/sys/class/speaker/mute +MUTED_MARKER=/tmp/system/muted POLL_INTERVAL=0.15 +# Lift Silent Mode's amp mute when the volume is raised above 0. +unmute_if_raised() { + vol="$1" + [ "$vol" -gt 0 ] 2>/dev/null || return 0 + [ -w "$SPEAKER_MUTE_FILE" ] || return 0 + [ "$(cat "$SPEAKER_MUTE_FILE" 2>/dev/null)" = "1" ] || return 0 + echo 0 > "$SPEAKER_MUTE_FILE" + rm -f "$MUTED_MARKER" + log_message "volume_sync_watchdog.sh: volume raised to $vol, cleared Silent Mode mute." +} + log_message "volume_sync_watchdog.sh: Started up." last="" @@ -34,6 +54,9 @@ while true; do # config-only, suppress spruce OSD (firmware shows its own) set_volume "$new_vol" true false fi + # A volume-up while Silent Mode muted the amp should bring + # sound back without flipping the switch. + unmute_if_raised "$new_vol" last="$new_vol" fi ;; From cb8b3d5e3c7e7bc34edb34b23742f71531243226 Mon Sep 17 00:00:00 2001 From: Felix MIL Date: Fri, 5 Jun 2026 15:32:07 +0200 Subject: [PATCH 7/9] Launch the Brick volume sync watchdog from the durable watchdog launcher - trimui_a133p.sh: - remove the volume_sync_watchdog launch from device_init_a133p, where the bare background job started during early-boot churn and did not survive - add a launch_startup_watchdogs override that runs the common watchdogs then starts volume_sync_watchdog alongside them, pinned to the system CPU like its siblings --- spruce/scripts/platform/device_functions/trimui_a133p.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spruce/scripts/platform/device_functions/trimui_a133p.sh b/spruce/scripts/platform/device_functions/trimui_a133p.sh index f101b8491..07b107891 100644 --- a/spruce/scripts/platform/device_functions/trimui_a133p.sh +++ b/spruce/scripts/platform/device_functions/trimui_a133p.sh @@ -175,9 +175,18 @@ device_init_a133p() { ) & amixer set 'Soft Volume Master' 255 # reset this to max so we're not double attenuating vol with two different mixer controls run_trimui_blobs "trimui_inputd trimui_scened trimui_btmanager hardwareservice musicserver" +} + +launch_startup_watchdogs() { + launch_common_startup_watchdogs_v2 + # Keep spruce's stored volume in sync with the firmware volume keys so the # in-UI volume bar tracks the hardware Volume +/- keys (including on hold). + # Launched here (not in device_init) so it lives alongside the other durable + # watchdogs and survives the early-boot churn; pinned like its siblings. /mnt/SDCARD/spruce/brick/volume_sync_watchdog.sh & + SYSTEM_CPU=${DEVICE_MAX_CORES_ONLINE%"${DEVICE_MAX_CORES_ONLINE#?}"} + pin_cpu "$SYSTEM_CPU" -n volume_sync_watchdog.sh & } set_event_arg_for_idlemon() { From 3ca761f1099319d4bd7c1a34a7a07f9f76decb3f Mon Sep 17 00:00:00 2001 From: Felix MIL Date: Fri, 5 Jun 2026 15:49:27 +0200 Subject: [PATCH 8/9] Make the Brick volume sync react instantly and stop double-writing config - volume_sync_watchdog.sh: - react to /tmp/system/set_volume via inotifywait instead of a 150ms poll, so the bar tracks held volume keys without the sampling delay - mirror only into SYSTEM_JSON (the file PyUI polls) and drop the set_volume call, since the firmware already owns the durable /mnt/UDISK copy and ALSA; this removes a redundant synchronous flash write from each ramp step - keep the Silent Mode unmute and add a polling fallback for when inotifywait is unavailable --- spruce/brick/volume_sync_watchdog.sh | 105 ++++++++++++++++++--------- 1 file changed, 72 insertions(+), 33 deletions(-) diff --git a/spruce/brick/volume_sync_watchdog.sh b/spruce/brick/volume_sync_watchdog.sh index 9b26ddeb6..c0d694b7c 100755 --- a/spruce/brick/volume_sync_watchdog.sh +++ b/spruce/brick/volume_sync_watchdog.sh @@ -3,17 +3,23 @@ # volume bar tracks the hardware Volume +/- keys, including while held. # # On the Brick the physical volume keys are handled by the stock firmware -# (trimui_inputd/keymon), which adjusts ALSA and writes the new level to -# /tmp/system/set_volume on every key event (autorepeat included). spruce never -# read that file back, so its own .vol (SYSTEM_JSON) only changed when spruce -# itself set the volume. The UI volume bar reads .vol, so holding a volume key -# moved the audio but not the bar (it updated once at most, never ramped). +# (trimui_inputd/hardwareservice), which owns /tmp/system/set_volume: it writes +# the new level there on every key event (autorepeat included) and applies it to +# ALSA and to its own config (/mnt/UDISK/system.json). spruce never read that +# file back, so its own .vol (SYSTEM_JSON) only changed when spruce itself set +# the volume. The UI volume bar reads .vol, so holding a volume key moved the +# audio but not the bar (it updated once at most, never ramped). # -# This watchdog polls /tmp/system/set_volume and, on each change, pushes the -# value through spruce's set_volume so .vol stays in sync and the bar redraws. -# set_volume is called config-only with the OSD suppressed: the firmware already -# draws its own volume OSD for the hardware keys, and set_volume is a no-op when -# the value is unchanged, so re-syncing the same number is harmless. +# This watchdog reacts to writes of /tmp/system/set_volume and mirrors the value +# into SYSTEM_JSON, the file PyUI's config watcher polls to redraw the bar. That +# is the watchdog's whole job: the durable copy and ALSA are the firmware's, so +# we deliberately do NOT call set_volume here (it would just re-poke the +# firmware's command file and add a redundant synchronous flash write). Keeping +# to a single in-place edit of SYSTEM_JSON is what lets the bar chase the OSD +# instead of crawling behind it while a key is held. +# +# Events are delivered by inotifywait so there is no fixed poll delay; if it is +# missing or exits we fall back to interval polling so syncing still works. # # It also lifts Silent Mode's hard mute on the first volume-up. Silent Mode mutes # the speaker amp (/sys/class/speaker/mute) and that mute is otherwise only @@ -24,10 +30,16 @@ . /mnt/SDCARD/spruce/scripts/helperFunctions.sh +SET_VOLUME_DIR=/tmp/system SET_VOLUME_FILE=/tmp/system/set_volume SPEAKER_MUTE_FILE=/sys/class/speaker/mute MUTED_MARKER=/tmp/system/muted -POLL_INTERVAL=0.15 +INOTIFYWAIT=/mnt/SDCARD/spruce/bin64/inotifywait +POLL_INTERVAL=0.15 # fallback polling cadence when inotifywait is unavailable + +mkdir -p "$SET_VOLUME_DIR" 2>/dev/null + +last_seen="" # Lift Silent Mode's amp mute when the volume is raised above 0. unmute_if_raised() { @@ -40,27 +52,54 @@ unmute_if_raised() { log_message "volume_sync_watchdog.sh: volume raised to $vol, cleared Silent Mode mute." } -log_message "volume_sync_watchdog.sh: Started up." +read_current() { + new_vol=$(cat "$SET_VOLUME_FILE" 2>/dev/null) + case "$new_vol" in + ''|*[!0-9]*) return 1 ;; # ignore empty or non-numeric writes + esac + echo "$new_vol" +} -last="" -while true; do - if [ -f "$SET_VOLUME_FILE" ]; then - new_vol=$(cat "$SET_VOLUME_FILE" 2>/dev/null) - case "$new_vol" in - ''|*[!0-9]*) ;; # ignore empty or non-numeric writes - *) - if [ "$new_vol" != "$last" ]; then - if [ "$new_vol" != "$(get_volume_level)" ]; then - # config-only, suppress spruce OSD (firmware shows its own) - set_volume "$new_vol" true false - fi - # A volume-up while Silent Mode muted the amp should bring - # sound back without flipping the switch. - unmute_if_raised "$new_vol" - last="$new_vol" - fi - ;; - esac +# Mirror one observed value into the bar (SYSTEM_JSON), in place, and handle the +# Silent Mode unmute. Cheap enough to run on every key event, even held. +sync_value() { + new_vol="$1" + [ "$new_vol" = "$last_seen" ] && return 0 + last_seen="$new_vol" + if [ "$new_vol" != "$(get_volume_level)" ]; then + sed -i "s/\"vol\":[[:space:]]*[0-9]*/\"vol\": $new_vol/" "$SYSTEM_JSON" 2>/dev/null fi - sleep "$POLL_INTERVAL" -done + unmute_if_raised "$new_vol" +} + +run_event_driven() { + # -m: stream events; watch the dir (catches in-place echo and atomic mv of + # the file). Filter to our filename below. Each event triggers a read of the + # current value, so a burst of writes coalesces to the latest level. + "$INOTIFYWAIT" -m -q -e modify -e close_write -e moved_to \ + --format '%f' "$SET_VOLUME_DIR" 2>/dev/null | \ + while read -r fname; do + [ "$fname" = "set_volume" ] || continue + cur="$(read_current)" || continue + sync_value "$cur" + done +} + +run_polling_fallback() { + log_message "volume_sync_watchdog.sh: inotifywait unavailable, using polling fallback." + while true; do + if [ -f "$SET_VOLUME_FILE" ]; then + cur="$(read_current)" && sync_value "$cur" + fi + sleep "$POLL_INTERVAL" + done +} + +log_message "volume_sync_watchdog.sh: Started up." + +if [ -x "$INOTIFYWAIT" ]; then + # If inotifywait ever exits, drop back to polling so syncing never stops. + run_event_driven + log_message "volume_sync_watchdog.sh: event loop ended, falling back to polling." +fi +run_polling_fallback From 68fd9e542f9b00067e40b0ea540ab8affff792de Mon Sep 17 00:00:00 2001 From: Felix MIL Date: Fri, 5 Jun 2026 15:52:56 +0200 Subject: [PATCH 9/9] Poll the Brick volume bar config faster so it tracks held volume keys - trim_ui_brick.py: - lower the system-config FileWatcher interval from 0.2s to 0.05s so the volume bar redraws within ~50ms of the volume sync watchdog mirroring a new level, instead of trailing by up to 200ms while a key is held --- App/PyUI/main-ui/devices/trimui/trim_ui_brick.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/App/PyUI/main-ui/devices/trimui/trim_ui_brick.py b/App/PyUI/main-ui/devices/trimui/trim_ui_brick.py index 649c8549a..d7b49bdfa 100644 --- a/App/PyUI/main-ui/devices/trimui/trim_ui_brick.py +++ b/App/PyUI/main-ui/devices/trimui/trim_ui_brick.py @@ -44,8 +44,14 @@ def __init__(self, device_name, main_ui_mode): self.ensure_wpa_supplicant_conf() threading.Thread(target=self.monitor_wifi, daemon=True).start() threading.Thread(target=self.startup_init, daemon=True).start() + # Poll briskly (50ms) so the volume bar tracks the hardware Volume + # keys closely: the firmware updates set_volume on every key event and + # the volume sync watchdog mirrors it into this file immediately, so a + # held key should see the bar chase the firmware OSD rather than trail + # it. A bare stat() per tick is cheap; the granularity-repeat window + # stays ~2s since it counts down in seconds. self.config_watcher_thread, self.config_watcher_thread_stop_event = FileWatcher().start_file_watcher( - "/mnt/SDCARD/Saves/trim-ui-brick-system.json", self.on_system_config_changed, interval=0.2, repeat_trigger_for_mtime_granularity_issues=True) + "/mnt/SDCARD/Saves/trim-ui-brick-system.json", self.on_system_config_changed, interval=0.05, repeat_trigger_for_mtime_granularity_issues=True) if(PyUiConfig.enable_button_watchers()): from controller.controller import Controller #/dev/miyooio if we want to get rid of miyoo_inputd