Skip to content

Feature Request/TrimUI Smart Pro S: Smart Flashlight Toggle for Quick Bar #1532

@AxiomAxon

Description

@AxiomAxon

Hi everyone!
Inspired by the flashlight feature on Smartwatches (Apple/Garmin/Samsung/Google), I had an idea for the upcoming spruceOS 4.2.1 Quick Bar. Since the TrimUI Smart Pro S has a very bright IPS screen and those great RGB rings, why not use them as a quick flashlight?
I wrote a small, clean toggle script that saves your current brightness, blasts everything to Max White, and restores your settings on the second click. It would be an amazing "Quality of Life" addition to the top bar toggles!

Here is the code:

bash
#!/bin/sh

# ---------------------------------------------------------
# Smart Flashlight Toggle v2.7.5
# Target: TrimUI Smart Pro S / spruceOS 4.1.2+
#
# Features:
#   - Quick Bar friendly on/off/long/toggle/status
#   - Atomic lock via directory (concurrency safe)
#   - Saves and restores exact previous hardware states
#   - Integer validation for stored values
#   - Safe thermal management (fan control)
#   - Optional amber marker file for external script sync
# ---------------------------------------------------------

# Paths for persistence and locking
BASE_DIR="/mnt/SDCARD/App/Flashlight"
STATE_FILE="$BASE_DIR/state.env"
LOCKDIR="/tmp/flashlight.lock"
AMBER_FILTER="/tmp/amber_active"

# Hardware Control Nodes
FAN_CTRL="/sys/class/hwmon/hwmon0/pwm1"
LED_CTRL="/sys/class/leds/led_ring/brightness"
DISPLAY_BACKLIGHT="/sys/class/backlight/backlight/brightness"

# --- Utility Functions ---

# Check if a hardware node exists and is writable
node_writable() {
    [ -e "$1" ] && [ -w "$1" ]
}

# Read hardware node value safely
read_node() {
    [ -r "$1" ] && cat "$1" 2>/dev/null
}

# Write value to hardware node safely
write_node() {
    node="$1"
    value="$2"
    node_writable "$node" && printf '%s\n' "$value" > "$node" 2>/dev/null
}

# Ensure input is a positive integer, otherwise use default
read_int_or_default() {
    value="$1"
    default="$2"
    case "$value" in
        ''|*[!0-9]*) echo "$default" ;;
        *) [ "$value" -lt 0 ] && echo "$default" || echo "$value" ;;
    esac
}

# Ensure integer does not exceed hardware limits
read_capped_int() {
    value="$1"
    cap="$2"
    case "$value" in
        ''|*[!0-9]*) echo "$cap" ;;
        *) [ "$value" -gt "$cap" ] && echo "$cap" || echo "$value" ;;
    esac
}

# Verify required hardware nodes and directories
check_env() {
    [ -d "$BASE_DIR" ] || {
        echo "Missing required directory: $BASE_DIR" >&2
        exit 1
    }

    for p in "$DISPLAY_BACKLIGHT" "$FAN_CTRL" "$LED_CTRL"; do
        [ -e "$p" ] || {
            echo "Missing required path: $p" >&2
            exit 1
        }
    done
}

# Use atomic directory creation to prevent multiple instances
acquire_lock() {
    if ! mkdir "$LOCKDIR" 2>/dev/null; then
        echo "Flashlight already active or lock exists ($LOCKDIR)." >&2
        exit 0
    fi
}

# --- State Management ---

# Backup current hardware settings before activation
save_state() {
    mkdir -p "$BASE_DIR" 2>/dev/null || return 1

    prev_backlight="$(read_int_or_default "$(read_node "$DISPLAY_BACKLIGHT")" 100)"
    prev_backlight="$(read_capped_int "$prev_backlight" 255)"

    prev_led="$(read_int_or_default "$(read_node "$LED_CTRL")" 0)"
    prev_led="$(read_capped_int "$prev_led" 255)"

    prev_fan="$(read_int_or_default "$(read_node "$FAN_CTRL")" 0)"
    prev_fan="$(read_capped_int "$prev_fan" 255)"

    # Export states to an environment file
    {
        echo "PREVIOUS_BRIGHTNESS=$prev_backlight"
        echo "PREVIOUS_LED=$prev_led"
        echo "PREVIOUS_FAN=$prev_fan"
    } > "$STATE_FILE" 2>/dev/null
}

# Load backed-up states into variables
load_state() {
    PREVIOUS_BRIGHTNESS=""
    PREVIOUS_LED=""
    PREVIOUS_FAN=""

    if [ -r "$STATE_FILE" ]; then
        . "$STATE_FILE" 2>/dev/null
        # Sanitize loaded values
        PREVIOUS_BRIGHTNESS="$(read_capped_int "$(read_int_or_default "$PREVIOUS_BRIGHTNESS" 100)" 255)"
        PREVIOUS_LED="$(read_capped_int "$(read_int_or_default "$PREVIOUS_LED" 0)" 255)"
        PREVIOUS_FAN="$(read_capped_int "$(read_int_or_default "$PREVIOUS_FAN" 0)" 255)"
    fi
}

# Handle amber filter signaling (marker file)
set_mode() {
    case "$1" in
        long) touch "$AMBER_FILTER" ;;
        *) rm -f "$AMBER_FILTER" 2>/dev/null ;;
    esac
}

# Get target brightness from config file or default to max
load_level() {
    if [ -r "$BASE_DIR/last_level.txt" ]; then
        saved="$(read_node "$BASE_DIR/last_level.txt")"
        read_capped_int "$(read_int_or_default "$saved" 255)" 255
    else
        echo 255
    fi
}

# Apply hardware changes for flashlight mode
apply_active_state() {
    # Boost fan speed for thermal safety (PWM 150)
    write_node "$FAN_CTRL" 150
    write_node "$LED_CTRL" "$ACTIVE_LED"
    write_node "$DISPLAY_BACKLIGHT" "$ACTIVE_BRIGHTNESS"
    set_mode "$1"
}

# Restore original hardware state and remove lock
cleanup() {
    if [ -r "$STATE_FILE" ]; then
        load_state
    fi

    # Gracefully restore LED, Fan, and Backlight
    if [ -n "$PREVIOUS_LED" ]; then
        write_node "$LED_CTRL" "$PREVIOUS_LED"
    else
        write_node "$LED_CTRL" 0
    fi

    if [ -n "$PREVIOUS_FAN" ]; then
        write_node "$FAN_CTRL" "$PREVIOUS_FAN"
    else
        write_node "$FAN_CTRL" 0
    fi

    if [ -n "$PREVIOUS_BRIGHTNESS" ]; then
        write_node "$DISPLAY_BACKLIGHT" "$PREVIOUS_BRIGHTNESS"
    fi

    # Cleanup temporary files and locks
    rm -f "$AMBER_FILTER" 2>/dev/null
    rm -f "$STATE_FILE" 2>/dev/null
    rmdir "$LOCKDIR" 2>/dev/null
}

# --- Main Logic ---

main_on() {
    save_state || exit 1
    load_state
    ACTIVE_LED="$(load_level)"
    ACTIVE_BRIGHTNESS="$ACTIVE_LED"
    apply_active_state "$1"
    # Ensure cleanup runs on exit
    trap cleanup EXIT INT TERM
}

main_off() {
    if [ -d "$LOCKDIR" ] || [ -r "$STATE_FILE" ]; then
        cleanup
    fi
}

main_status() {
    if [ -d "$LOCKDIR" ]; then
        echo "Smart Flashlight: ACTIVE"
    else
        echo "Smart Flashlight: INACTIVE"
    fi
}

# --- Command Router ---

case "$1" in
    on)
        check_env
        acquire_lock
        main_on white
        ;;

    long)
        check_env
        acquire_lock
        main_on long
        ;;

    off)
        main_off
        ;;

    toggle)
        if [ -d "$LOCKDIR" ]; then
            main_off
        else
            "$0" on
        fi
        ;;

    status)
        main_status
        ;;

    *)
        echo "Usage: $0 {on|long|off|toggle|status}" >&2
        exit 1
        ;;
esac

exit 0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions