Skip to content

Instantly share code, notes, and snippets.

@Konfekt
Last active February 13, 2026 21:08
Show Gist options
  • Select an option

  • Save Konfekt/d57567b84b264e37e62bd5c001e76457 to your computer and use it in GitHub Desktop.

Select an option

Save Konfekt/d57567b84b264e37e62bd5c001e76457 to your computer and use it in GitHub Desktop.
A shell script to map all Wacom devices to a given monitor, in particular to switch through all available monitors by repeatedly calling it with the parameter NEXT
#!/usr/bin/env bash
# exit on error or on use of undeclared variable or pipe error:
set -o errexit -o errtrace -o nounset -o pipefail
# optionally debug output by supplying TRACE=1
[[ "${TRACE:-0}" == "1" ]] && set -o xtrace
# Bash 4.4+ supports inherit_errexit; this improves error handling in functions and subshells. If unavailable, ignore.
shopt -s inherit_errexit 2>/dev/null || true
PS4='+\t '
IFS=$'\n\t'
# Trap errors and show surrounding context to help debugging.
error_handler() {
local line="${1:-0}"
local caller_lines="${2:-}"
local cmd="${3:-}"
local status="${4:-1}"
local start=$(( line > 3 ? line - 3 : 1 ))
echo >&2 "Error: In ${BASH_SOURCE[0]}, Lines $line and $caller_lines, Command $cmd exited with Status $status"
if command -v pr >/dev/null 2>&1; then
pr -tn "${BASH_SOURCE[0]}" | tail -n+"$start" | head -n7 | sed '4s/^\s*/>> /' >&2 || true
fi
exit "$status"
}
trap 'error_handler $LINENO "$BASH_LINENO" "$BASH_COMMAND" $?' ERR
usage() {
cat << EOF
Usage: $0
Map all Wacom Devices to Monitor .
For example, to map to Monitor VGA-1:
$0 VGA-1
If is NEXT, then to the next monitor
as listed by xrandr. Useful, for example, to bind
a key to, say by xbindkeys, to
notify-send "\$($0 NEXT)"
EOF
exit 1
}
# Return a list of active monitor names (one per line) using xrandr.
# Uses --listactivemonitors (XRandR 1.5+); falls back to parsing --query on older versions.
get_active_monitors() {
local out
if out=$(xrandr --listactivemonitors 2>/dev/null); then
printf '%s\n' "$out" | awk 'NR>1 {print $NF}'
else
xrandr --query | awk '/ connected( |$)/ {print $1}'
fi
}
next() {
# Since
# xsetwacom --get "Wacom Pad pad" MapToOutput
# returns
# 'MapToOutput' is a write-only option.
# we store the output device in a temp file.
# This, woefully, cannot detect output device changes;
# for example, after a reboot.
local tmpdir="${TMPDIR:-${TMP:-/tmp}}"
local WFILE="$tmpdir/wacom2mon"
local current_monitor=""
if [[ -r "$WFILE" ]]; then
IFS= read -r current_monitor < "$WFILE" || current_monitor=""
fi
mapfile -t monitors < <(get_active_monitors)
if (( ${#monitors[@]} == 0 )); then
echo >&2 "No active monitors detected via xrandr."
return 1
fi
local next_monitor=""
if [[ -z "$current_monitor" ]]; then
# no mapping known, just use the first monitor
next_monitor="${monitors[0]}"
else
# cycle to the next monitor, wrapping around at the end
local idx=-1
for i in "${!monitors[@]}"; do
if [[ "${monitors[i]}" == "$current_monitor" ]]; then
idx=$i
break
fi
done
if (( idx < 0 )); then
next_monitor="${monitors[0]}"
else
local next_index=$(( (idx + 1) % ${#monitors[@]} ))
next_monitor="${monitors[next_index]}"
fi
fi
printf '%s\n' "$next_monitor" > "$WFILE"
printf '%s\n' "$next_monitor"
}
command -v xsetwacom >/dev/null 2>&1 || exit 1
command -v xrandr >/dev/null 2>&1 || exit 1
if [[ -z "${DISPLAY:-}" ]]; then
echo >&2 "Error: DISPLAY is not set; run this script inside an X11 session."
exit 1
fi
if [[ $# == 0 ]]; then
usage
else
screen="$1"
fi
if [[ "${screen^^}" = "NEXT" ]]; then
screen="$(next)"
fi
# Extract device names robustly by removing everything from " id:" to the end. This preserves spaces in names.
devices="$(xsetwacom --list devices | sed -E 's/[[:space:]]+id:.*$//')"
[ -z "$devices" ] && exit 1
# See https://wiki.archlinux.org/title/Graphics_tablet#Mapping_the_tablet_to_a_monitor
# and https://askubuntu.com/questions/547990/how-to-configure-wacom-tablet-area-on-ubuntu-14-04/548019#548019
while read -r d; do
# instead of `while IFS=$' \n\t'`; from https://stackoverflow.com/a/15398846
xsetwacom --set "$d" MapToOutput "$screen" &&
echo "$d was mapped to: $screen"
done <<< "$devices"
@j-hap

j-hap commented Sep 3, 2023 •

Copy link
Copy Markdown

Thanks for the script! Here's some suggestions: instead of taking the last two entries from the xrandr output, you can keep them all if you just delete the first line with sed 1d. I also took the liberty of changing the way a current mapping is found, so it's not limited to bash and its arrays

next() {
  [ -n "$TMPDIR" ] || TMPDIR=/tmp
  WFILE="$TMPDIR/wacom2mon"

  # Since
  #   xsetwacom --get "Wacom Pad pad" MapToOutput
  # returns
  #   'MapToOutput' is a write-only option.
  # we store the output device in a temp file.
  # This, woefully, cannot detect output device changes;
  # for example, after a reboot.

  if [ -f "$WFILE" ]; then
    current_monitor="$(cat "$WFILE")"
  else
    current_monitor=""
  fi

  if [ -z current_monitor ]; then
    # no mapping known, just use the first monitor
    next_monitor=$(xrandr --listactivemonitors | sed -n 2p | awk '{print $4}')
  else
    # duplicate the output in case the current monitor is the last one, so we automatically cycle
    # through. head after grep takes the first match and sed afterwards drops the match and keeps
    # only the next monitor
    next_monitor=$(xrandr --listactivemonitors | sed 1d | awk '{print $4}' | tee /dev/stdout | grep -A 1 "$current_monitor" | sed -n 2p)
  fi

  echo "$next_monitor" >$WFILE
  echo "$next_monitor"
}

this still fails if the stored monitor is not found anymore though

@Konfekt

Konfekt commented Sep 3, 2023 •

Copy link
Copy Markdown
Author

Glad to have been of help. Thank you for your improvements as well, which have just been added where [ -z current_monitor ] was replaced by [ -z $current_monitor ].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment