Is the trigger for selecting menu items not working on BTT itself?

Hi,
I wanted to have some sort of automation that activates with a shortcut to check for updates on BTT. Because the menu's name keeps changing all the time, I could not simply use the Mac's own shortcut feature to add a keyboard shortcut to the menu.

So I came up with the idea to use the selecting from menu trigger, but it seems not to work with BTT. When I tested it with Finder, it worked. So I thought maybe @Andreas_Hegenberg, you could fix that, or what would be much better you could add a shortcut to BTT for checking for updates.

And I know it is a silly request, but I thought, why not just ask?
Thanks

Hi @Black_Simorgh,

If I understand your post correctly, you're trying to figure out how to setup a Trigger in BTT that would run some Actions to check if there are any updates available in BTT? Essentially, an automated way of clicking on the Check For Regular Updates or the Check For Alpha Version Updates menu items in the BTT menu bar like in the screenshot below. Is this correct?

First, regarding your issue with the "selecting from menu" trigger not working with BTT itself - that's an interesting problem. You're right that it works with other apps like Finder but not with BTT. While we wait for @Andreas_Hegenberg to consider your feature request for a direct update shortcut, I have an alternative solution that might be even more reliable.

I'll share with you a shell script I wrote to automate the process of sharing my BTT device details which includes information about whether my installed version of BTT is behind for any regular or alpha updates. The advantage of this approach is that it directly checks the update sources online rather than depending on UI elements, making it more reliable than menu selection.

#!/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 "$@"

For my setup, I have a Key Sequences / Typed Words trigger in BTT such that whenever I type /BTTD, it triggers a Run Shell Script action (which executes $HOME/projects/BTT/get_BTT_info.sh) followed by a Send Keyboard Shortcut action (specifically, ⌘ V) so that the /BTTD text gets replaced by the following:


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

How the script works: It reads your installed BTT version from the application bundle, then fetches the latest available alpha and regular versions from the BTT websites. It compares these versions to determine if you're behind on updates. This approach bypasses the UI entirely, which makes it more reliable than trying to navigate menus.

You could use the same script for your needs. For you, you could setup a Keyboard Shortcut trigger combined with a Run Shell Script action to display a notification with information about if there are any updates. For example:

alpha_updates="$($HOME/projects/BTT/get_BTT_info.sh -o json | jq -r '.Alpha')"
osascript -e "display notification \"$alpha_updates\" with title \"get_BTT_info.sh\""

Feel free to customize the notification text or modify the script to show only what you need. For example, you could change it to only show notifications when updates are actually available, or format the message differently.

I just tested my script and it works. Here's a full BTT configuration JSON. Whenever you press fn F9, you'll see a notification saying, for example, "0 update(s) behind latest Alpha version 5.252 (2025030501)."

[
  {
    "BTTActionCategory" : 0,
    "BTTLastUpdatedAt" : 1741312247.5872312,
    "BTTTriggerType" : 0,
    "BTTTriggerClass" : "BTTTriggerTypeKeyboardShortcut",
    "BTTUUID" : "0847DA89-AAEE-437F-A30B-6CA0C8860072",
    "BTTPredefinedActionType" : 366,
    "BTTPredefinedActionName" : "Empty Placeholder",
    "BTTAdditionalConfiguration" : "8388608",
    "BTTKeyboardShortcutKeyboardType" : 2302,
    "BTTTriggerOnDown" : 1,
    "BTTLayoutIndependentChar" : "F9",
    "BTTEnabled" : 1,
    "BTTEnabled2" : 1,
    "BTTShortcutKeyCode" : 101,
    "BTTShortcutModifierKeys" : 8388608,
    "BTTOrder" : 1,
    "BTTAutoAdaptToKeyboardLayout" : 0,
    "BTTAdditionalActions" : [
      {
        "BTTActionCategory" : 0,
        "BTTLastUpdatedAt" : 1741312514.4169679,
        "BTTTriggerParentUUID" : "0847DA89-AAEE-437F-A30B-6CA0C8860072",
        "BTTIsPureAction" : true,
        "BTTTriggerClass" : "BTTTriggerTypeKeyboardShortcut",
        "BTTUUID" : "5C62B509-C161-48C2-A9EF-3FDFE18E897F",
        "BTTPredefinedActionType" : 206,
        "BTTPredefinedActionName" : "Execute Shell Script  or  Task",
        "BTTShellTaskActionScript" : "alpha_updates=\"$($HOME\/projects\/BTT\/get_BTT_info.sh -o json | jq -r '.Alpha')\"\nosascript -e \"display notification \\\"$alpha_updates\\\" with title \\\"get_BTT_info.sh\\\"\"\n",
        "BTTShellTaskActionConfig" : "\/bin\/bash:::-c:::-:::",
        "BTTKeyboardShortcutKeyboardType" : 0,
        "BTTEnabled" : 1,
        "BTTEnabled2" : 1,
        "BTTShortcutKeyCode" : -1,
        "BTTOrder" : 729,
        "BTTAutoAdaptToKeyboardLayout" : 0
      }
    ]
  }
]

I hope this makes sense! Let me know if you have any trouble.

@Andreas_Hegenberg it would be very cool if BTT provided these as dynamic variables:

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

Hi @Black_Simorgh ,

You can use this if BTT's window is the focused one:

I expected this to always work:

But for some reason only the 1st action is being executed, which is strange.

Yeah,
You understood that correctly. But honestly, this is way too overwhelming and confusing for me. Besides that, I rather like to run the script from within the app, whether the shortcuts app or the BTT. I don't like to have the script being on a separate location.

I thought it would be much easier than that.

I already tried that, but it doesn't seem to work. I set up a keyboard shortcut for it, so when the BTT app is in front, I can run it, but it doesn't work.

Do you see the trigger in "Recenty used"? Since for me this is working.

1 Like

:man_facepalming: Ok, you know what the issue was?
Instead of typing BetterTouchtool, I typed BetterTouchTool, like it is showing in the menubar. And because of that, it didn't work.
And I also noticed, that I just could type the numbers. So like (2);(4)

Anyway, I think now I have it.
Thanks

It's going to work either way, even betterTouchtool will work. I guess the magic was that you used the number, like I've shown in my screenshot.

Yeah, maybe.