├── LICENSE ├── README.md └── xinput-toggle /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 4 | software, either in source code form or as a compiled binary, for any purpose, 5 | commercial or non-commercial, and by any means. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | xinput-toggle is a simple script to toggle devices on and off for X11 systems. 2 | It was created to toggle Yubikey state, but it can deal with any X input 3 | device. 4 | 5 | ## Usage 6 | 7 | `xinput-toggle -h` contains detailed usage information. 8 | 9 | I suggest you bind xinput-toggle to a keybinding in your window manager if you 10 | will use it a lot. 11 | 12 | For these examples I will show this being used to enable/disable a Yubikey 13 | (`-r` is a case-insensitive regex used to identify devices by names, as shown 14 | by `xinput list`). 15 | 16 | ``` 17 | # Toggle device status (on -> off, off -> on) 18 | xinput-toggle -r yubikey 19 | 20 | # Show notification indicating actions performed 21 | xinput-toggle -r yubikey -n 22 | 23 | # Disable xinput (no toggle) 24 | xinput-toggle -r yubikey -d 25 | 26 | # Enable xinput (no toggle) 27 | xinput-toggle -r yubikey -e 28 | 29 | # Enable for 5 seconds, then disable again 30 | xinput-toggle -r yubikey -e -t 5 31 | ``` 32 | 33 | ## Requirements 34 | 35 | - xinput 36 | - bash 4+ 37 | - notify-send (optional, for `-n`, provided with libnotify) 38 | -------------------------------------------------------------------------------- /xinput-toggle: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | debug() { 4 | # DEBUG comes from the environment 5 | if (( DEBUG )); then 6 | printf '%s\n' "$@" >&2 7 | fi 8 | } 9 | 10 | is_enabled() { 11 | local id="${1?}" 12 | enabled=$( 13 | xinput list-props "$id" | 14 | grep '\bDevice Enabled\b' | sed 's/.*\(.\)$/\1/' 15 | ) 16 | # xinput returns 0 for disabled and 1 for enabled, so we invert since we 17 | # pass on 0 18 | return "$(( !enabled ))" 19 | } 20 | 21 | should_disable() { 22 | local id="${1?}" 23 | local force_enable="${2?}" 24 | local force_disable="${3?}" 25 | 26 | if (( force_enable )) && (( force_disable )); then 27 | echo '-d and -e make no sense together' >&2 28 | exit 3 29 | fi 30 | 31 | if (( force_enable )); then 32 | return 1 33 | elif (( force_disable )); then 34 | return 0 35 | elif is_enabled "$id"; then 36 | return 0 37 | else 38 | return 1 39 | fi 40 | } 41 | 42 | get_id_for_device_name() { 43 | local name="${1?}" 44 | xinput list "$name" | sed -n 's/.*id=\([0-9]\+\).*/\1/p' 45 | } 46 | 47 | show_help() { 48 | cat << EOF 49 | Usage: ${0##*/} [-n] 50 | 51 | Enable and disable xinput devices. 52 | 53 | -d disable only, do not toggle 54 | -e enable only, do not toggle 55 | -h show this help page 56 | -i XID only operate on device with xinput id XID 57 | -n show results using notify-send in addition to stdout 58 | -p print what we would do, but don't actually do it 59 | -r REGEX only operate on devices matching name REGEX 60 | -s exit with status 0 on no match (by default, status 2 is used) 61 | -t SECONDS revert enable/disable after SECONDS seconds, ie. if you were 62 | enabling, after SECONDS seconds it will be disabled again. 63 | SECONDS must be an integer greater than 0 64 | EOF 65 | } 66 | 67 | act_on_device() { 68 | local print="${1?}" 69 | local notify="${2?}" 70 | local timeout="${3?}" 71 | local xinput_action="${4?}" 72 | local our_next_flag="${5?}" 73 | local id="${6?}" 74 | 75 | if (( print )); then 76 | echo "Would $xinput_action device with id $id" 77 | else 78 | if ! msg="$(xinput -"$xinput_action" "$id" 2>&1)"; then 79 | printf '%s\n' "$msg" >&2 80 | notify-send "$msg" 81 | exit 6 82 | fi 83 | 84 | if (( notify )); then 85 | notify-send "${xinput_action^}d device with id $id" 86 | fi 87 | 88 | if (( timeout )); then 89 | debug "Added timeout $timeout" 90 | { 91 | sleep "$timeout" 92 | # TODO: Make args_without_timeour_or_force non-global (sigh, 93 | # shell makes this difficult due to pass by value) 94 | "$0" "${args_without_timeout_or_force[@]}" \ 95 | "$our_next_flag" -i "$id" 96 | } & 97 | fi 98 | fi 99 | } 100 | 101 | notify=0 102 | force_enable=0 103 | force_disable=0 104 | timeout=0 105 | print=0 106 | only_xid=0 107 | fail_on_no_match=1 108 | input_ids=() 109 | args_without_timeout_or_force=() 110 | 111 | while getopts dehi:npr:t:s opt; do 112 | # We need to get a list of args without timeout/force for when we execute 113 | # timeout reversions. This is a blacklist of things we should not put in 114 | # the array, since they force enable/disable/name or do the timeout itself. 115 | case "$opt" in 116 | d|e|t|r) 117 | : # Blacklisted 118 | ;; 119 | *) 120 | args_without_timeout_or_force+=( -"$opt" ) 121 | if [[ $OPTARG ]]; then 122 | args_without_timeout_or_force+=( "$OPTARG" ) 123 | fi 124 | ;; 125 | esac 126 | 127 | case "$opt" in 128 | 'd') force_disable=1 ;; 129 | 'e') force_enable=1 ;; 130 | 'n') notify=1 ;; 131 | 'i') only_xid="$OPTARG" ;; 132 | 'h') 133 | show_help 134 | exit 0 135 | ;; 136 | 'p') print=1 ;; 137 | 'r') regex="$OPTARG" ;; 138 | 's') fail_on_no_match=0 ;; 139 | 't') timeout="$OPTARG" ;; 140 | '?') 141 | show_help >&2 142 | exit 1 143 | ;; 144 | esac 145 | done 146 | 147 | if (( only_xid )) && [[ "$regex" ]]; then 148 | echo '-r and -i cannot be currently used together' >&2 149 | exit 4 150 | elif ! (( only_xid )) && ! [[ "$regex" ]]; then 151 | echo 'either -r or -i must be passed to filter devices' >&2 152 | exit 5 153 | fi 154 | 155 | if (( only_xid )); then 156 | input_ids=( "$only_xid" ) 157 | else 158 | mapfile -t matched_device_names < <( 159 | xinput list | 160 | sed -n 's/.*[↳∼] \(.*\)id=.*\[.*slave.*/\1/p' | 161 | sed 's/[\t ]*$//' | 162 | grep -i -- "$regex" 163 | ) 164 | 165 | debug 'Matched device names:' "${matched_device_names[@]}" 166 | 167 | for name in "${matched_device_names[@]}"; do 168 | input_ids+=( "$(get_id_for_device_name "$name")" ) 169 | done 170 | fi 171 | 172 | if (( "${#input_ids[@]}" == 0 )); then 173 | msg='No matching devices found (filters:' 174 | (( only_xid )) && msg+=" id=$only_xid" 175 | [[ $regex ]] && msg+=" regex=$regex" 176 | msg+=')' 177 | 178 | echo "$msg" >&2 179 | (( notify )) && notify-send "$msg" 180 | if (( fail_on_no_match )); then 181 | exit 2 182 | else 183 | exit 0 184 | fi 185 | fi 186 | 187 | for id in "${input_ids[@]}"; do 188 | if should_disable "$id" "$force_enable" "$force_disable"; then 189 | act_on_device "$print" "$notify" "$timeout" disable -e "$id" 190 | else 191 | act_on_device "$print" "$notify" "$timeout" enable -d "$id" 192 | fi 193 | done 194 | 195 | # We may have queued some timeout reversion jobs 196 | wait 197 | --------------------------------------------------------------------------------