Last active
February 13, 2026 21:08
-
-
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
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 | |
| # 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 |
|
| 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" |
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
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 arraysthis still fails if the stored monitor is not found anymore though