Skip to content

Instantly share code, notes, and snippets.

@milichev
Last active January 30, 2026 18:06
Show Gist options
  • Select an option

  • Save milichev/3de6d332ee056374858d9a4377fa5053 to your computer and use it in GitHub Desktop.

Select an option

Save milichev/3de6d332ee056374858d9a4377fa5053 to your computer and use it in GitHub Desktop.
Shell script for placing external display logically related to the main MacBook display

place-external-display.sh

Shell script for placing external display logically related to the main MacBook display

This script makes it easy to set hotkeys for switching the related logical position of an external display using displayplacer.

Installation

  1. Install the main part:
brew install displayplacer
  1. Place the .sh files to a utility directory ~/Scripts.

  2. Create a hotkey bound. Karabiner-ELements can be used for this. See example of the configuration in karabiner.json file.

{
"profiles": [
{
"complex_modifications": {
"rules": [
{
"description": "Ctrl+Opt+Cmd+Up to place external display above",
"manipulators": [
{
"from": {
"key_code": "up_arrow",
"modifiers": {
"mandatory": [
"left_control",
"left_option",
"left_command"
],
"optional": ["any"]
}
},
"to": [
{
"shell_command": "~/Scripts/place-external-display.sh top"
}
],
"type": "basic"
}
]
},
{
"description": "Ctrl+Opt+Cmd+Right to place external display to the right",
"manipulators": [
{
"from": {
"key_code": "right_arrow",
"modifiers": {
"mandatory": [
"left_control",
"left_option",
"left_command"
],
"optional": ["any"]
}
},
"to": [
{
"shell_command": "~/Scripts/place-external-display.sh right"
}
],
"type": "basic"
}
]
}
]
},
"name": "Default profile",
"selected": true,
"virtual_hid_keyboard": { "keyboard_type_v2": "ansi" }
}
]
}
#!/usr/bin/env bash
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/ui-utils.sh"
POSITION="${1:-top}"
# Validate position
case "$POSITION" in
top | right | bottom | left) ;;
*)
shalert "Invalid position" "Allowed: top, right, bottom, left. Default: top"
exit 1
;;
esac
# Parse displayplacer list output
DISPLAY_LIST=$(displayplacer list)
# Extract displays: map contextual_id -> (persistent_id, resolution, scaling, current_origin)
declare -A DISPLAYS
while IFS= read -r line; do
# Contextual ID
if echo "$line" | grep -q "Contextual screen id:"; then
CTX_ID=$(echo "$line" | awk '{print $NF}')
fi
# Persistent ID
if echo "$line" | grep -q "Persistent screen id:"; then
PERSIST_ID=$(echo "$line" | awk '{print $NF}')
fi
# Resolution
if echo "$line" | grep -q "^Resolution:"; then
WIDTH=$(echo "$line" | awk -F'[x:]' '{print $2}' | tr -d ' ')
HEIGHT=$(echo "$line" | awk -F'[x:]' '{print $3}' | tr -d ' ')
fi
# Scaling
if echo "$line" | grep -q "^Scaling:"; then
SCALING=$(echo "$line" | awk '{print $NF}')
fi
# Origin: parse "(x,y)"
if echo "$line" | grep -q "^Origin:"; then
ORIGIN_X=$(echo "$line" | sed -E 's/.*\(([^,]+),.*/\1/')
ORIGIN_Y=$(echo "$line" | sed -E 's/.*,([^)]+)\).*/\1/')
# Store this display's info
DISPLAYS[$CTX_ID]="$PERSIST_ID|$WIDTH|$HEIGHT|$SCALING|$ORIGIN_X|$ORIGIN_Y"
fi
done <<<"$DISPLAY_LIST"
# Validate we have at least 2 displays
if [ ${#DISPLAYS[@]} -lt 2 ]; then
shalert "Display Detection Failed" "Found ${#DISPLAYS[@]} display(s), need at least 2"
exit 1
fi
# Extract MacBook (contextual 1) and external (contextual 2) specs
IFS='|' read -r MB_ID MB_W MB_H MB_SCALE MB_OX MB_OY <<<"${DISPLAYS[1]}"
IFS='|' read -r EXT_ID EXT_W EXT_H EXT_SCALE _ _ <<<"${DISPLAYS[2]}"
if [ -z "$MB_ID" ] || [ -z "$EXT_ID" ]; then
shalert "Display Parsing Failed" "Could not extract primary displays"
exit 1
fi
# Calculate new external display origin based on position
# MacBook always at (0, 0)
case "$POSITION" in
top)
NEW_EXT_X=0
NEW_EXT_Y=$((-EXT_H))
;;
right)
NEW_EXT_X=$MB_W
NEW_EXT_Y=0
;;
bottom)
NEW_EXT_X=0
NEW_EXT_Y=$MB_H
;;
left)
NEW_EXT_X=$((-EXT_W))
NEW_EXT_Y=0
;;
esac
# Build displayplacer arguments
MB_ARG="id:$MB_ID res:${MB_W}x${MB_H} scaling:$MB_SCALE origin:(0,0)"
EXT_ARG="id:$EXT_ID res:${EXT_W}x${EXT_H} scaling:$EXT_SCALE origin:($NEW_EXT_X,$NEW_EXT_Y)"
printf "place-external-display: %s\nMacBook: %s\nExternal: %s\n" "$POSITION" "$MB_ARG" "$EXT_ARG"
displayplacer "$MB_ARG" "$EXT_ARG"
shnotif "External Display" "Placed to the $POSITION"
#!/bin/bash
function shalert() {
local title="${1:-Debug}"
local message="${2:-}"
# Escape quotes in all arguments
title=$(printf '%s\n' "$title" | sed 's/"/\\"/g')
message=$(printf '%s\n' "$message" | sed 's/"/\\"/g')
osascript -e "display alert \"$title\" message \"$message\"" >/dev/null 2>&1
}
function shnotif() {
local title="${1:-Debug}"
local message="${2:-}"
local subtitle="${3:-}"
# Escape quotes in all arguments
title=$(printf '%s\n' "$title" | sed 's/"/\\"/g')
message=$(printf '%s\n' "$message" | sed 's/"/\\"/g')
osascript -e "display notification \"$message\" with title \"$title\" subtitle \"$subtitle\"" >/dev/null 2>&1
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment