Running action "Switch to Preset / Enable Specific Presets" doesn't update displayed profile in the UI

Describe the bug

Steps to reproduce:

  1. Have at least a "main" and a "secondary" profile
  2. Have the "main" profile selected as the master
  3. Create a new action to select the "secondary" profile as the master profile
  4. Trigger the action
  5. BUG: The displayed "Preset: main" in the upper right corner of the BTT window doesn't update. If you click the profile, you can see the "secondary" profile is "logically" selected by the action, but the UI doesn't get updated immediately. I can only see the UI updated if I restart BTT.

Hi @thalesmello,

This unexpected behavior you're reporting reminds me of the fourth bug I reported in this post. I think it's the same bug.

@thalesmello Could you please provide your BTT device information? This is necessary for bug reporting.


Device Details:

  • Device: MacBook Pro (14-inch, 2021)
  • macOS: 15.3.1 (24D70)
  • BTT Version: 5.262 (2025031004)
  • Alpha: 0 update(s) behind latest Alpha version 5.262 (2025031004).
  • Regular: Installed version is ahead of latest Regular version 5.140.

@fortred2 Is it generated somewhere I can copy and paste?

@thalesmello Normally, BTT users manually populate this information when creating bug reports on this forum. However, I decided to write a bash script CLI to automatically generate this information.

:information_source: Note: I've been using this script for several months and it works well, but please let me know if you find any bugs.

Here's how I use it – I have a Key Sequences / Typed Words Trigger configured in BTT such that when I typed /BTTD, BTT will run the below bash script and replace the /BTTD text with my device info like what you saw in my previous post.

Here is the bash script:

#!/bin/bash

###############################################################################
# BetterTouchTool Version Checker CLI - With Improved Structured Logging
###############################################################################
# Purpose: This script checks your installed BetterTouchTool version and compares
# it with the latest available alpha and regular versions. It provides formatted
# output for easy sharing of your BTT environment information.
#
# INSTALLATION & USAGE INSTRUCTIONS:
# 1. Save this file as "get_BTT_info.sh" in a location of your choice
# 2. Make the script executable by running this command in Terminal:
#    chmod +x /path/to/get_BTT_info.sh
# 3. Run the script using one of these methods:
#    a) ./get_BTT_info.sh (if you're in the same directory)
#    b) /path/to/get_BTT_info.sh (using the full or relative path)
#    c) bash /path/to/get_BTT_info.sh (if you didn't make it executable)
#
# By default, output is copied to clipboard in markdown format for easy forum posting
# Use -h or --help to see all available options
###############################################################################

# Default configurations
log_level="NONE"           # Options: NONE, ERROR, WARNING, INFO, DEBUG
output_format="markdown"   # Options: markdown, plain, json
copy_to_clipboard=true     # Set to false with '-n' or '--no-copy'
colorize_logs=false        # Enable colored logging with '--color'

# Initialize variables for installed version and build
installed_version=""
installed_build=""

# Track if we had to increment the build
incremented_build=false
original_installed_build=""

# Required utilities that must be available on the system
required_utilities=(
  "curl"                     # For fetching online version information
  "grep"                     # For text pattern matching
  "awk"                      # For text processing
  "sed"                      # For string manipulation
  "ioreg"                    # For hardware information
  "defaults"                 # For reading macOS property lists
  "/usr/libexec/PlistBuddy"  # For advanced property list manipulation
  "sw_vers"                  # For macOS version information
  "pbcopy"                   # For copying output to clipboard
)

# ANSI Color Codes (if --color is enabled)
RED="\033[0;31m"
GREEN="\033[0;32m"
BLUE="\033[0;34m"
YELLOW="\033[0;33m"
WHITE="\033[0;37m"
RESET="\033[0m"

# Display help and usage information
usage() {
  cat << EOF
Usage: $0 [options]

Basic Options:
  -l, --log-level LEVEL             Set the logging level (NONE, ERROR, WARNING, INFO, DEBUG). Default: NONE
  -o, --output-format FORMAT        Set output format (markdown, plain, json). Default: markdown
  -c, --copy                        Copy output to clipboard (enabled by default)
  -n, --no-copy                     Do not copy output to clipboard
  --color                           Enable colored logs
  -h, --help                        Display this help message and exit

Advanced/Testing Options:
  -v, --installed-version VERSION   Manually specify BTT version (bypasses auto-detection)
  -b, --installed-build BUILD       Manually specify BTT build (bypasses auto-detection)

Examples:
  $0                              # Auto-detect installed BTT version
  $0 -l DEBUG -o plain            # Set log level to DEBUG and output format to plain text
  $0 -o json -n                   # Set output format to JSON and do not copy to clipboard
  $0 --color                       # Enable colored logs
  $0 -v 4.702 -b 2024091705       # Testing: Simulate a specific BTT version/build

EOF
  exit 1
}

# Convert log level string to numeric value for comparison
log_level_num() {
  case "$1" in
    "NONE") echo -1 ;;
    "ERROR") echo 0 ;;
    "WARNING") echo 1 ;;
    "INFO") echo 2 ;;
    "DEBUG") echo 3 ;;
    *) echo -1 ;;  # Default to NONE
  esac
}

# Logging function with configurable levels and formatting
log() {
  # Field widths for alignment
  local level_width=20
  local func_width=25

  local caller_function="${FUNCNAME[1]}"
  [ -z "$caller_function" ] && caller_function="main"

  local level=$1
  shift
  local message="$*"
  local current_level
  current_level=$(log_level_num "$log_level")
  local message_level
  message_level=$(log_level_num "$level")

  # Only output if message level is less than or equal to current log level
  if [ "$current_level" -ge "$message_level" ]; then
    local color=""
    if [ "$colorize_logs" = true ]; then
      case "$level" in
        "ERROR") color="$RED" ;;
        "WARNING") color="$YELLOW" ;;
        "INFO") color="$GREEN" ;;
        "DEBUG") color="$BLUE" ;;
        *) color="$WHITE" ;;
      esac

      printf "%-${level_width}b %-${func_width}b %b\n" \
        "[$color$level$RESET]" \
        "[$caller_function]" \
        "${WHITE}$message$RESET" >&2
    else
      printf "%-${level_width}s %-${func_width}s %s\n" \
        "[$level]" \
        "[$caller_function]" \
        "$message" >&2
    fi
  fi
}

# Check if all required utilities are available on the system
check_requirements() {
  local missing_utilities=()
  for util in "${required_utilities[@]}"; do
    util_path=$(command -v "$util")
    if [ -x "$util_path" ]; then
      log "DEBUG" "Found utility '$util' at '$util_path'"
    else
      log "ERROR" "Required utility '$util' not found in PATH."
      missing_utilities+=("$util")
    fi
  done

  # Exit if any required utilities are missing
  if [ "${#missing_utilities[@]}" -ne 0 ]; then
    log "ERROR" "Missing utilities: ${missing_utilities[*]}"
    exit 1
  fi
  
}

# Compare two version numbers (e.g., 4.702 vs 4.705)
# Returns 1 if ver1 > ver2, 0 if equal, -1 if ver1 < ver2
version_compare() {
  local ver1="$1"
  local ver2="$2"

  IFS='.' read -ra ver1_parts <<< "$ver1"
  IFS='.' read -ra ver2_parts <<< "$ver2"

  local max_index=$(( ${#ver1_parts[@]} > ${#ver2_parts[@]} ? ${#ver1_parts[@]} : ${#ver2_parts[@]} ))

  # Compare each part of the version number
  for ((i=0; i<max_index; i++)); do
    local part1=${ver1_parts[i]:-0}
    local part2=${ver2_parts[i]:-0}

    if ((10#$part1 > 10#$part2)); then
      log "DEBUG" "$ver1 > $ver2"
      
      echo 1
      return
    elif ((10#$part1 < 10#$part2)); then
      log "DEBUG" "$ver1 < $ver2"
      
      echo -1
      return
    fi
  done

  log "DEBUG" "$ver1 == $ver2"
  
  echo 0
}

# Get installed BTT version from the application bundle
get_installed_version() {
  if [ -z "$installed_version" ] || [ -z "$installed_build" ]; then
    # Read version and build from BTT's Info.plist
    installed_version=$(defaults read "/Applications/BetterTouchTool.app/Contents/Info.plist" CFBundleShortVersionString 2>/dev/null)
    installed_build=$(defaults read "/Applications/BetterTouchTool.app/Contents/Info.plist" CFBundleVersion 2>/dev/null)
  fi

  # Exit if BTT is not installed or inaccessible
  if [ -z "$installed_version" ] || [ -z "$installed_build" ]; then
    log "ERROR" "BetterTouchTool not installed or Info.plist inaccessible."
    exit 1
  fi

  log "DEBUG" "installed_version: $installed_version"
  log "DEBUG" "installed_build: $installed_build"

  original_installed_build="$installed_build"
  installed_version_full="$installed_version ($installed_build)"

  log "DEBUG" "installed_version_full: $installed_version_full"
  
}

# Fetch available alpha versions from the BTT website
fetch_alpha_versions() {
  # Parse the releases page to extract alpha version information
  available_versions=$(curl -s https://folivora.ai/releases/ | grep -o '"btt[0-9]\+\.[0-9]\+-[0-9]\+\.zip"')
  formatted_versions=$(echo "$available_versions" | sed -E 's/"btt([0-9]+\.[0-9]+)-([0-9]+)\.zip"/\1 (\2)/')

  # Get the latest alpha version (first in the list)
  latest_alpha_version=$(echo "$formatted_versions" | head -n 1)

  # Log available alpha versions for debugging
  number_of_alpha_versions_to_log=10
  log "DEBUG" "Available alpha versions (displaying $number_of_alpha_versions_to_log most recent): $(echo "$formatted_versions" | head -n $number_of_alpha_versions_to_log | tr '\n' ' ')"
  
}

# Determine status relative to alpha versions
determine_alpha_status() {
  # Find the position of installed version in the available alpha versions list
  installed_index=$(echo "$formatted_versions" | awk -v installed="$installed_version_full" '
  {
      if ($0 == installed) {
          print NR;
          exit
      }
  }')

  # Set the message based on whether the installed version was found
  if [ -z "$installed_index" ]; then
    log "WARNING" "Installed version not found in available alpha versions."
    alpha_version_message="Installed alpha version not found."
  else
    log "DEBUG" "Installed version found at index $installed_index"
    alpha_updates_behind=$((installed_index - 1))
    alpha_version_message="$alpha_updates_behind update(s) behind latest Alpha version $latest_alpha_version."
  fi
  
}

# Fetch the latest regular version from the BTT website
fetch_regular_version() {
  log "INFO" "Fetching latest regular version..."
  # Extract version numbers from the release notes page
  regular_versions=$(curl -s https://updates.folivora.ai/bettertouchtool_release_notes.html | grep -Eo 'BetterTouchTool >= [0-9]+\.[0-9]+' | sed -E 's/BetterTouchTool >= //g' | sort -V)
  latest_regular_version=$(echo "$regular_versions" | tail -n 1)

  log "INFO" "Latest regular version: $latest_regular_version"
  
}

# Compare installed version with the latest regular version
determine_regular_status() {
  # Compare versions using the version_compare function
  comparison_result=$(version_compare "$installed_version" "$latest_regular_version")

  # Set message based on comparison result
  if [ "$comparison_result" -eq 0 ]; then
    regular_version_message="Up to date with latest Regular version $latest_regular_version."
  elif [ "$comparison_result" -eq 1 ]; then
    regular_version_message="Installed version is ahead of latest Regular version $latest_regular_version."
  else
    regular_version_message="Installed version is behind latest Regular version $latest_regular_version."
  fi

  log "INFO" "$regular_version_message"
  
}

# Get system information (device model and macOS version)
get_system_info() {
  # Get device model information
  device_info="$(/usr/libexec/PlistBuddy -c 'Print :0:product-name' /dev/stdin <<< "$(ioreg -arc IOPlatformDevice -k product-name)")"
  
  # Get macOS version information
  macos_build="$(sw_vers -buildVersion)"
  macos_product_version="$(sw_vers -productVersion)"

  # Determine if running a beta version of macOS
  if [[ "$macos_build" =~ [a-zA-Z]$ ]]; then
    macos_version="$macos_product_version Beta ($macos_build)"
  else
    macos_version="$macos_product_version ($macos_build)"
  fi

  log "DEBUG" "device_info: $device_info"
  log "DEBUG" "macos_version: $macos_version"
  
}

# Generate the final output in the specified format
generate_output() {
  # Handle special case where build number was incremented
  if [ "$incremented_build" = true ]; then
    installed_version_full="$installed_version ($original_installed_build)"
    alpha_version_message="1 uninstallable update behind latest Alpha version $latest_alpha_version"
  fi

  # Generate output based on the selected format
  if [ "$output_format" == "markdown" ]; then
    output_content=$(cat << EOF
- **Device**: $device_info
- **macOS**: $macos_version
- **BTT Version**: $installed_version_full
- **Alpha**: $alpha_version_message
- **Regular**: $regular_version_message
EOF
)
  elif [ "$output_format" == "plain" ]; then
    output_content=$(cat << EOF
Device: $device_info
macOS: $macos_version
BTT Version: $installed_version_full
Alpha: $alpha_version_message
Regular: $regular_version_message
EOF
)
  elif [ "$output_format" == "json" ]; then
    output_content=$(printf "{\n  \"Device\": \"%s\",\n  \"macOS\": \"%s\",\n  \"BTT_Version\": \"%s\",\n  \"Alpha\": \"%s\",\n  \"Regular\": \"%s\"\n}" \
      "$device_info" \
      "$macos_version" \
      "$installed_version_full" \
      "$alpha_version_message" \
      "$regular_version_message")
  else
    log "ERROR" "Unsupported output format: $output_format"
    exit 1
  fi

  # Print the output to standard output
  echo "$output_content"

  # Copy to clipboard if enabled
  if [ "$copy_to_clipboard" = true ]; then
    echo "$output_content" | pbcopy
    log "INFO" "Output copied to clipboard."
  fi
  
}

# Main function to orchestrate the script execution
main() {
  # Parse command-line arguments
  while [[ "$#" -gt 0 ]]; do
    case $1 in
      -v|--installed-version)
        installed_version="$2"
        shift 2
        ;;
      -b|--installed-build)
        installed_build="$2"
        shift 2
        ;;
      -l|--log-level)
        log_level="$2"
        shift 2
        ;;
      -o|--output-format)
        output_format="$2"
        shift 2
        ;;
      -c|--copy)
        copy_to_clipboard=true
        shift
        ;;
      -n|--no-copy)
        copy_to_clipboard=false
        shift
        ;;
      --color)
        colorize_logs=true
        shift
        ;;
      -h|--help)
        usage
        ;;
      *)
        log "ERROR" "Unknown parameter passed: $1"
        usage
        ;;
    esac
  done

  # Validate log level
  case "$log_level" in
    NONE|ERROR|WARNING|INFO|DEBUG)
      ;;
    *)
      log "ERROR" "Invalid log level: $log_level"
      usage
      ;;
  esac

  # Validate output format
  case "$output_format" in
    markdown|plain|json)
      ;;
    *)
      log "ERROR" "Invalid output format: $output_format"
      usage
      ;;
  esac

  # Log the configuration
  log "INFO" "Configuration: log_level=$log_level, output_format=$output_format, copy_to_clipboard=$copy_to_clipboard, installed_version=$installed_version, installed_build=$installed_build, colorize_logs=$colorize_logs"

  # Execute the main workflow
  check_requirements
  get_installed_version
  fetch_alpha_versions
  determine_alpha_status

  # Handle the case where installed version wasn't found in alpha versions
  if [[ "$alpha_version_message" == "Installed alpha version not found." ]]; then
    log "DEBUG" "Installed version not found. Trying with incremented build number..."
    installed_build=$((installed_build + 1))
    installed_version_full="$installed_version ($installed_build)"
    incremented_build=true
    log "DEBUG" "Retesting with incremented installed_build: $installed_version_full"
    determine_alpha_status
  fi

  fetch_regular_version
  determine_regular_status
  get_system_info
  generate_output

  log "INFO" "Script completed successfully."
}

# Start the script
main "$@"

To replicate my exact setup as I described above (i.e. Key Sequences / Typed Words Trigger whenever I type /BTTD), you can copy the JSON below and then paste it into the GUI of BTT.

[
  {
    "BTTActionCategory" : 0,
    "BTTLastUpdatedAt" : 1741310204.9550328,
    "BTTTriggerType" : 624,
    "BTTTriggerTypeDescriptionReadOnly" : "Please Select a Trigger ",
    "BTTTriggerClass" : "BTTTriggerTypeKeySequence",
    "BTTUUID" : "61A1AE2F-C6E0-4FEC-925C-FF65E0D338F9",
    "BTTPredefinedActionType" : 366,
    "BTTPredefinedActionName" : "Empty Placeholder",
    "BTTEnabled" : 1,
    "BTTEnabled2" : 1,
    "BTTOrder" : 0,
    "BTTAdditionalActions" : [
      {
        "BTTActionCategory" : 0,
        "BTTLastUpdatedAt" : 1741311265.283988,
        "BTTTriggerParentUUID" : "61A1AE2F-C6E0-4FEC-925C-FF65E0D338F9",
        "BTTIsPureAction" : true,
        "BTTTriggerClass" : "BTTTriggerTypeKeySequence",
        "BTTUUID" : "72868EBC-85DD-442E-A7D0-94EE24F92F4C",
        "BTTPredefinedActionType" : 206,
        "BTTPredefinedActionName" : "Execute Shell Script  or  Task",
        "BTTShellTaskActionScript" : "$HOME\/projects\/BTT\/get_BTT_info.sh",
        "BTTShellTaskActionConfig" : "\/bin\/bash:::-c:::-:::",
        "BTTEnabled" : 1,
        "BTTEnabled2" : 1,
        "BTTOrder" : 730
      },
      {
        "BTTActionCategory" : 0,
        "BTTLastUpdatedAt" : 1741310097.5546122,
        "BTTTriggerParentUUID" : "61A1AE2F-C6E0-4FEC-925C-FF65E0D338F9",
        "BTTIsPureAction" : true,
        "BTTTriggerClass" : "BTTTriggerTypeKeySequence",
        "BTTUUID" : "3784EC61-68EF-4F45-A0D9-245D73240A39",
        "BTTLayoutIndependentActionChar" : "v",
        "BTTShortcutToSend" : "55,9",
        "BTTEnabled" : 1,
        "BTTEnabled2" : 1,
        "BTTOrder" : 731
      }
    ],
    "BTTKeySequence" : {
      "BTTPauseBetween" : 0.6015528549382716,
      "BTTCharactersToDeleteAfterwards" : 5,
      "BTTKeyCount" : 11,
      "BTTKeySequenceDownKeys" : [
        {
          "BTTKEYCharacter" : " \/",
          "BTTKEYCode" : 44,
          "BTTKEYDown" : 1,
          "BTTKEYOrderRelevant" : 1,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : "r⇧ ",
          "BTTKEYCode" : 60,
          "BTTKEYDown" : 1,
          "BTTKEYOrderRelevant" : 1,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : " B",
          "BTTKEYCode" : 11,
          "BTTKEYDown" : 1,
          "BTTKEYOrderRelevant" : 1,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : " T",
          "BTTKEYCode" : 17,
          "BTTKEYDown" : 1,
          "BTTKEYOrderRelevant" : 1,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : " T",
          "BTTKEYCode" : 17,
          "BTTKEYDown" : 1,
          "BTTKEYOrderRelevant" : 1,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : " D",
          "BTTKEYCode" : 2,
          "BTTKEYDown" : 1,
          "BTTKEYOrderRelevant" : 1,
          "BTTKEYRequired" : 1
        }
      ],
      "BTTKeySequenceMixedKeys" : [
        {
          "BTTKEYCharacter" : " \/",
          "BTTKEYCode" : 44,
          "BTTKEYDown" : 1,
          "BTTKEYOrderRelevant" : 1,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : " \/",
          "BTTKEYCode" : 44,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : "r⇧ ",
          "BTTKEYCode" : 60,
          "BTTKEYDown" : 1,
          "BTTKEYOrderRelevant" : 1,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : " B",
          "BTTKEYCode" : 11,
          "BTTKEYDown" : 1,
          "BTTKEYOrderRelevant" : 1,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : " B",
          "BTTKEYCode" : 11,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : " T",
          "BTTKEYCode" : 17,
          "BTTKEYDown" : 1,
          "BTTKEYOrderRelevant" : 1,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : " T",
          "BTTKEYCode" : 17,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : " T",
          "BTTKEYCode" : 17,
          "BTTKEYDown" : 1,
          "BTTKEYOrderRelevant" : 1,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : " T",
          "BTTKEYCode" : 17,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : " D",
          "BTTKEYCode" : 2,
          "BTTKEYDown" : 1,
          "BTTKEYOrderRelevant" : 1,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : " D",
          "BTTKEYCode" : 2,
          "BTTKEYRequired" : 1
        }
      ],
      "BTTKeySequenceUpKeys" : [
        {
          "BTTKEYCharacter" : " \/",
          "BTTKEYCode" : 44,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : " B",
          "BTTKEYCode" : 11,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : " T",
          "BTTKEYCode" : 17,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : " T",
          "BTTKEYCode" : 17,
          "BTTKEYRequired" : 1
        },
        {
          "BTTKEYCharacter" : " D",
          "BTTKEYCode" : 2,
          "BTTKEYRequired" : 1
        }
      ]
    },
    "BTTTriggerConfig" : {
      "BTTHUDText" : "Triggered",
      "BTTShowHUD" : 1
    }
  }
]

I hope this helps!