Last active
August 1, 2026 23:56
-
-
Save matheusmota/5b990e0bd8352a6562847d6c4c9b642f to your computer and use it in GitHub Desktop.
displaymanager - how to select and disable a monitor on macos
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Traps Ctrl+C to ensure the cursor is restored if the user aborts the script | |
| trap 'tput cnorm; echo -e "\nAborted."; exit 1' INT | |
| # 1. Alias Installation Check | |
| if [[ "$1" != "--skip-install" ]]; then | |
| user_shell=$(basename "$SHELL") | |
| if [[ "$user_shell" == "zsh" ]]; then | |
| config_file="$HOME/.zshrc" | |
| elif [[ "$user_shell" == "bash" ]]; then | |
| config_file="$HOME/.bashrc" | |
| else | |
| config_file="" | |
| fi | |
| if [[ -n "$config_file" ]]; then | |
| touch "$config_file" | |
| if grep -q "alias displaymanager=" "$config_file"; then | |
| echo "The 'displaymanager' shortcut is already installed in your $config_file." | |
| read -r -p "Do you want to reinstall/update it? (y/n): " install_alias | |
| else | |
| echo "The 'displaymanager' shortcut is not installed." | |
| read -r -p "Do you want to install it in your $config_file? (y/n): " install_alias | |
| fi | |
| if [[ "$install_alias" =~ ^[yY]$ ]]; then | |
| sed -i '' '/alias displaymanager=/d' "$config_file" | |
| gist_url="https://gist.github.com/matheusmota/5b990e0bd8352a6562847d6c4c9b642f/raw" | |
| echo "alias displaymanager='bash <(curl -sL \"$gist_url\") --skip-install'" >> "$config_file" | |
| echo "Alias installed successfully! (It will be available in your next terminal session or after running: source $config_file)" | |
| echo "------------------------------" | |
| else | |
| echo "Alias installation skipped." | |
| echo "------------------------------" | |
| fi | |
| fi | |
| fi | |
| # 2. Checks if displayplacer is installed | |
| if ! command -v displayplacer &> /dev/null; then | |
| echo "'displayplacer' was not found on your system." | |
| read -r -p "Do you want to install it now using Homebrew? (y/n): " install_choice | |
| if [[ "$install_choice" =~ ^[yY]$ ]]; then | |
| if ! command -v brew &> /dev/null; then | |
| echo "Error: Homebrew is not installed. Please install Homebrew first." | |
| exit 1 | |
| fi | |
| echo "Installing displayplacer..." | |
| brew install displayplacer | |
| else | |
| echo "Installation cancelled. The script requires displayplacer to work." | |
| exit 1 | |
| fi | |
| fi | |
| # 3. Interactive Menu Function | |
| choose_option() { | |
| local prompt="$1" | |
| shift | |
| local options=("$@") | |
| local selected=0 | |
| echo -e "\n\033[1;36m$prompt\033[0m" # Cyan bold color | |
| for _ in "${options[@]}"; do echo ""; done | |
| echo -en "\033[${#options[@]}A" | |
| tput civis # Hides cursor | |
| while true; do | |
| tput sc # Saves cursor position | |
| for i in "${!options[@]}"; do | |
| if [[ $i -eq $selected ]]; then | |
| echo -e "\033[32m ❯ ${options[$i]}\033[0m\033[K" | |
| else | |
| echo -e " ${options[$i]}\033[K" | |
| fi | |
| done | |
| read -rsn1 key | |
| if [[ $key == $'\033' ]]; then | |
| read -rsn2 key2 | |
| if [[ $key2 == '[A' ]]; then # Up arrow | |
| ((selected--)) | |
| [[ $selected -lt 0 ]] && selected=$((${#options[@]} - 1)) | |
| elif [[ $key2 == '[B' ]]; then # Down arrow | |
| ((selected++)) | |
| [[ $selected -ge ${#options[@]} ]] && selected=0 | |
| fi | |
| elif [[ $key == "" ]]; then # Enter key | |
| break | |
| fi | |
| tput rc | |
| done | |
| tput cnorm | |
| tput rc | |
| for _ in "${options[@]}"; do echo ""; done | |
| return $selected | |
| } | |
| # 4. Captures and parses monitor data with CACHE Memory | |
| CACHE_FILE="$HOME/.displaymanager_cache" | |
| cache_ids=() | |
| cache_types=() | |
| cache_res=() | |
| if [[ -f "$CACHE_FILE" ]]; then | |
| while IFS="|" read -r c_id c_type c_res; do | |
| if [[ -n "$c_id" ]]; then | |
| cache_ids+=("$c_id") | |
| cache_types+=("$c_type") | |
| cache_res+=("$c_res") | |
| fi | |
| done < "$CACHE_FILE" | |
| fi | |
| update_cache() { | |
| local new_id="$1" | |
| local new_type="$2" | |
| local new_res="$3" | |
| local found=false | |
| for i in "${!cache_ids[@]}"; do | |
| if [[ "${cache_ids[$i]}" == "$new_id" ]]; then | |
| cache_types[$i]="$new_type" | |
| cache_res[$i]="$new_res" | |
| found=true | |
| break | |
| fi | |
| done | |
| if [[ "$found" == "false" ]]; then | |
| cache_ids+=("$new_id") | |
| cache_types+=("$new_type") | |
| cache_res+=("$new_res") | |
| fi | |
| } | |
| active_ids=() | |
| current_id="" | |
| current_type="" | |
| current_res="" | |
| current_enabled="" | |
| # Reads displayplacer output line by line securely | |
| while IFS= read -r raw_line; do | |
| # Clears any hidden carriage returns from macOS output | |
| line="${raw_line//$'\r'/}" | |
| if [[ "$line" == Persistent\ screen\ id:\ * ]]; then | |
| if [[ -n "$current_id" ]]; then | |
| update_cache "$current_id" "$current_type" "$current_res" | |
| if [[ "$current_enabled" == "true" ]]; then | |
| active_ids+=("$current_id") | |
| fi | |
| fi | |
| current_id="${line#Persistent screen id: }" | |
| # STRIP ALL HIDDEN WHITESPACES to avoid corrupted UUIDs | |
| current_id="${current_id//[[:space:]]/}" | |
| current_type="Unknown" | |
| current_res="Unknown" | |
| current_enabled="Unknown" | |
| elif [[ "$line" == Type:\ * ]]; then | |
| current_type="${line#Type: }" | |
| elif [[ "$line" == Resolution:\ * ]]; then | |
| current_res="${line#Resolution: }" | |
| current_res="${current_res//[[:space:]]/}" | |
| elif [[ "$line" == Enabled:\ * ]]; then | |
| current_enabled="${line#Enabled: }" | |
| current_enabled="${current_enabled//[[:space:]]/}" | |
| fi | |
| done <<< "$(displayplacer list)" | |
| # Catches the last monitor block | |
| if [[ -n "$current_id" ]]; then | |
| update_cache "$current_id" "$current_type" "$current_res" | |
| if [[ "$current_enabled" == "true" ]]; then | |
| active_ids+=("$current_id") | |
| fi | |
| fi | |
| # Saves sanitized data to cache | |
| > "$CACHE_FILE" | |
| for i in "${!cache_ids[@]}"; do | |
| echo "${cache_ids[$i]}|${cache_types[$i]}|${cache_res[$i]}" >> "$CACHE_FILE" | |
| done | |
| ids=() | |
| displays=() | |
| for i in "${!cache_ids[@]}"; do | |
| c_id="${cache_ids[$i]}" | |
| ids+=("$c_id") | |
| is_active=false | |
| for act_id in "${active_ids[@]}"; do | |
| if [[ "$act_id" == "$c_id" ]]; then | |
| is_active=true | |
| break | |
| fi | |
| done | |
| if $is_active; then | |
| state_text="ON" | |
| else | |
| state_text="OFF" | |
| fi | |
| displays+=("${cache_types[$i]} [Res: ${cache_res[$i]}] - Status: $state_text") | |
| done | |
| if [ ${#ids[@]} -eq 0 ]; then | |
| echo "No monitors found or an error occurred while reading displays." | |
| exit 1 | |
| fi | |
| displays+=("Exit") | |
| # 5. Main Execution Loop | |
| while true; do | |
| choose_option "Select a monitor to manage:" "${displays[@]}" | |
| monitor_idx=$? | |
| if [[ $monitor_idx -eq $((${#displays[@]} - 1)) ]]; then | |
| echo "Exiting..." | |
| exit 0 | |
| fi | |
| selected_id="${ids[$monitor_idx]}" | |
| # Sub-menu for action | |
| choose_option "Action for ${cache_types[$monitor_idx]}:" "Enable (Turn on)" "Disable (Turn off)" "Back" | |
| action_idx=$? | |
| if [[ $action_idx -eq 0 ]]; then | |
| echo -e "Enabling monitor..." | |
| displayplacer "id:$selected_id enabled:true" | |
| break | |
| elif [[ $action_idx -eq 1 ]]; then | |
| echo -e "Disabling monitor..." | |
| displayplacer "id:$selected_id enabled:false" | |
| break | |
| elif [[ $action_idx -eq 2 ]]; then | |
| continue | |
| fi | |
| done | |
| echo -e "Command executed successfully!" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment