Created
June 18, 2025 05:17
-
-
Save bcdonadio/70b5aed200bc467aadce2e688f2ebf80 to your computer and use it in GitHub Desktop.
Properly map a Wacom tablet to a given screen, even on Fedora 42.
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 | |
| # ---------- USER TWEAKABLE CONSTANTS ---------- | |
| PATTERN="Wacom Intuos Pro L Pen" # substring to search for (case-insensitive) | |
| MON="DP-4" # xrandr output to map to | |
| ANCHOR="BOTTOM" # FULL|LEFT|RIGHT|TOP|BOTTOM | |
| # --------------------------------------------- | |
| set -euo pipefail | |
| # Function to get monitor information | |
| get_monitor_info() { | |
| local monitor="$1" | |
| local monitor_line | |
| monitor_line=$(xrandr | grep "^$monitor " | head -1) | |
| if [[ -z "$monitor_line" ]]; then | |
| echo "Error: Monitor '$monitor' not found in xrandr output" >&2 | |
| echo "Available monitors:" >&2 | |
| xrandr | grep " connected" >&2 | |
| exit 1 | |
| fi | |
| # Extract resolution and position from xrandr output | |
| # Format: "DP-4 connected 2560x1440+3840+0 (normal left inverted right x axis y axis) 597mm x 336mm" | |
| local resolution_part | |
| resolution_part=$(echo "$monitor_line" | grep -o '[0-9]*x[0-9]*+[0-9]*+[0-9]*') | |
| local width height offset_x offset_y | |
| width=$(echo "$resolution_part" | cut -d'x' -f1) | |
| height=$(echo "$resolution_part" | cut -d'x' -f2 | cut -d'+' -f1) | |
| offset_x=$(echo "$resolution_part" | cut -d'+' -f2) | |
| offset_y=$(echo "$resolution_part" | cut -d'+' -f3) | |
| echo "$width $height $offset_x $offset_y" | |
| } | |
| # Function to find matching xinput devices | |
| find_xinput_devices() { | |
| local pattern="$1" | |
| local device_ids | |
| device_ids=$(xinput list | grep -i "$pattern" | grep -o 'id=[0-9]*' | cut -d'=' -f2) | |
| if [[ -z "$device_ids" ]]; then | |
| echo "Error: No xinput devices found matching pattern '$pattern'" >&2 | |
| echo "Available devices:" >&2 | |
| xinput list >&2 | |
| exit 1 | |
| fi | |
| echo "$device_ids" | |
| } | |
| # Function to get device tablet area | |
| get_device_area() { | |
| local device_id="$1" | |
| # Try to get tablet area from device properties | |
| local area_line | |
| area_line=$(xinput list-props "$device_id" | grep -i "tablet area" || true) | |
| if [[ -n "$area_line" ]]; then | |
| # Extract tablet area coordinates | |
| local coords | |
| coords=$(echo "$area_line" | grep -o '[0-9]*[[:space:]]*,[[:space:]]*[0-9]*[[:space:]]*,[[:space:]]*[0-9]*[[:space:]]*,[[:space:]]*[0-9]*') | |
| local x1 y1 x2 y2 | |
| x1=$(echo "$coords" | cut -d',' -f1 | tr -d ' ') | |
| y1=$(echo "$coords" | cut -d',' -f2 | tr -d ' ') | |
| x2=$(echo "$coords" | cut -d',' -f3 | tr -d ' ') | |
| y2=$(echo "$coords" | cut -d',' -f4 | tr -d ' ') | |
| local width=$((x2 - x1)) | |
| local height=$((y2 - y1)) | |
| echo "$width $height" | |
| else | |
| # Fallback: try to get from coordinate transformation matrix bounds | |
| # or use reasonable defaults based on device name | |
| echo "Error: Could not determine tablet area for device $device_id" >&2 | |
| echo "Device properties:" >&2 | |
| xinput list-props "$device_id" >&2 | |
| exit 1 | |
| fi | |
| } | |
| # Function to apply transformation to device | |
| apply_transformation() { | |
| local device_id="$1" | |
| local screen_w="$2" screen_h="$3" screen_x="$4" screen_y="$5" | |
| local tablet_w="$6" tablet_h="$7" | |
| local anchor="$8" | |
| # Calculate screen aspect ratio | |
| local screen_ar | |
| screen_ar=$(echo "scale=6; $screen_w / $screen_h" | bc -l) | |
| # Calculate tablet aspect ratio | |
| local tablet_ar | |
| tablet_ar=$(echo "scale=6; $tablet_w / $tablet_h" | bc -l) | |
| # Determine usable tablet area that maintains screen aspect ratio | |
| local usable_w usable_h | |
| if (( $(echo "$screen_ar > $tablet_ar" | bc -l) )); then | |
| # Screen is wider - use full tablet width, partial height | |
| usable_w="$tablet_w" | |
| usable_h=$(echo "scale=6; $tablet_w / $screen_ar" | bc -l) | |
| else | |
| # Screen is taller - use full tablet height, partial width | |
| usable_h="$tablet_h" | |
| usable_w=$(echo "scale=6; $tablet_h * $screen_ar" | bc -l) | |
| fi | |
| # Calculate position of usable area within tablet based on ANCHOR | |
| local tablet_start_x tablet_start_y | |
| case "$anchor" in | |
| FULL) | |
| # Center the usable area within the tablet | |
| tablet_start_x=$(echo "scale=6; ($tablet_w - $usable_w) / 2" | bc -l) | |
| tablet_start_y=$(echo "scale=6; ($tablet_h - $usable_h) / 2" | bc -l) | |
| ;; | |
| LEFT) | |
| # Anchor to left edge | |
| tablet_start_x="0" | |
| tablet_start_y=$(echo "scale=6; ($tablet_h - $usable_h) / 2" | bc -l) | |
| ;; | |
| RIGHT) | |
| # Anchor to right edge | |
| tablet_start_x=$(echo "scale=6; $tablet_w - $usable_w" | bc -l) | |
| tablet_start_y=$(echo "scale=6; ($tablet_h - $usable_h) / 2" | bc -l) | |
| ;; | |
| TOP) | |
| # Anchor to top edge | |
| tablet_start_x=$(echo "scale=6; ($tablet_w - $usable_w) / 2" | bc -l) | |
| tablet_start_y="0" | |
| ;; | |
| BOTTOM) | |
| # Anchor to bottom edge | |
| tablet_start_x=$(echo "scale=6; ($tablet_w - $usable_w) / 2" | bc -l) | |
| tablet_start_y=$(echo "scale=6; $tablet_h - $usable_h" | bc -l) | |
| ;; | |
| esac | |
| # Apply coordinate transformation matrix that maps the usable tablet area to the target screen | |
| # Get total desktop dimensions for normalization | |
| local desktop_w desktop_h | |
| desktop_w=$(xrandr | grep "current" | grep -o "current [0-9]* x [0-9]*" | cut -d' ' -f2) | |
| desktop_h=$(xrandr | grep "current" | grep -o "current [0-9]* x [0-9]*" | cut -d' ' -f4) | |
| # Calculate transformation matrix that maps the usable tablet area to the screen | |
| # The matrix needs to: | |
| # 1. Scale the tablet coordinates so that the usable area maps to screen size | |
| # 2. Translate so that the usable area maps to the screen position | |
| local c0 c1 c2 c3 c4 c5 c6 c7 c8 | |
| # Scale factors: screen size relative to the usable tablet area, normalized by desktop | |
| c0=$(echo "scale=6; ($screen_w * $tablet_w) / ($usable_w * $desktop_w)" | bc -l) | |
| c1="0" | |
| # X translation: position screen on desktop, accounting for tablet anchor offset | |
| c2=$(echo "scale=6; ($screen_x - $tablet_start_x * $screen_w / $usable_w) / $desktop_w" | bc -l) | |
| c3="0" | |
| # Y scale factor: screen height relative to usable tablet area, normalized by desktop | |
| c4=$(echo "scale=6; ($screen_h * $tablet_h) / ($usable_h * $desktop_h)" | bc -l) | |
| # Y translation: position screen on desktop, accounting for tablet anchor offset | |
| c5=$(echo "scale=6; ($screen_y - $tablet_start_y * $screen_h / $usable_h) / $desktop_h" | bc -l) | |
| c6="0" | |
| c7="0" | |
| c8="1" | |
| echo "Applying coordinate transformation matrix:" | |
| echo " Usable tablet area: ${usable_w}x${usable_h} at offset ${tablet_start_x},${tablet_start_y} (anchor: $anchor)" | |
| echo " Target screen area: ${screen_w}x${screen_h} at offset ${screen_x},${screen_y}" | |
| echo " Desktop size: ${desktop_w}x${desktop_h}" | |
| echo " Matrix: $c0 $c1 $c2 $c3 $c4 $c5 $c6 $c7 $c8" | |
| xinput set-prop "$device_id" "Coordinate Transformation Matrix" $c0 $c1 $c2 $c3 $c4 $c5 $c6 $c7 $c8 | |
| } | |
| # Main execution | |
| main() { | |
| echo "Wacom Tablet Mapping Script" | |
| echo "Pattern: $PATTERN" | |
| echo "Monitor: $MON" | |
| echo "Anchor: $ANCHOR" | |
| echo "" | |
| # Check for required commands | |
| if ! command -v xrandr &> /dev/null; then | |
| echo "Error: xrandr command not found" >&2 | |
| exit 1 | |
| fi | |
| if ! command -v xinput &> /dev/null; then | |
| echo "Error: xinput command not found" >&2 | |
| exit 1 | |
| fi | |
| if ! command -v bc &> /dev/null; then | |
| echo "Error: bc command not found (required for calculations)" >&2 | |
| exit 1 | |
| fi | |
| # Get monitor information | |
| echo "Getting monitor information..." | |
| local monitor_info | |
| monitor_info=$(get_monitor_info "$MON") | |
| read -r screen_w screen_h screen_x screen_y <<< "$monitor_info" | |
| echo "Monitor $MON: ${screen_w}x${screen_h} at offset +${screen_x}+${screen_y}" | |
| # Find matching devices | |
| echo "Finding xinput devices matching '$PATTERN'..." | |
| local device_ids | |
| device_ids=$(find_xinput_devices "$PATTERN") | |
| echo "Found devices: $device_ids" | |
| # Process each device | |
| for device_id in $device_ids; do | |
| echo "" | |
| echo "Processing device $device_id..." | |
| # Get device name for reference | |
| local device_name | |
| device_name=$(xinput list | grep "id=$device_id" | sed 's/.*↳//; s/id=.*//' | xargs) | |
| echo "Device name: $device_name" | |
| # Get tablet area | |
| local tablet_info | |
| tablet_info=$(get_device_area "$device_id") | |
| read -r tablet_w tablet_h <<< "$tablet_info" | |
| echo "Tablet area: ${tablet_w}x${tablet_h}" | |
| # Apply transformation using the new method | |
| apply_transformation "$device_id" "$screen_w" "$screen_h" "$screen_x" "$screen_y" "$tablet_w" "$tablet_h" "$ANCHOR" | |
| echo "Transformation applied successfully" | |
| done | |
| echo "" | |
| echo "Wacom mapping completed successfully!" | |
| } | |
| # Run main function | |
| main "$@" |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Yeah. Vibe-coding. Just wanted to get this shit working again. I noticed that it only works for two monitors, but fuck it, I don't have more than that. Blame Sonnet 4.