forked from MestreLion/scripts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
monitor-switch
executable file
·232 lines (208 loc) · 6.5 KB
/
monitor-switch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#!/bin/bash
#
# monitor-switch - switch outputs using xrand
#
# Copyright (C) 2012 Rodrigo Silva (MestreLion) <linux@rodrigosilva.com>
# 2015 Jarno Suni <8@iki.fi>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. See <http://www.gnu.org/licenses/gpl.html>
declare -A monitor_opts
declare -a monitors
myname="${0##*/}"
verbose=0
# Read settings from config file
config=${XDG_CONFIG_HOME:-"$HOME"/.config}/"$myname".conf
if [[ -f "$config" ]]; then
source "$config"
fi
hex_to_ascii() {
echo -n "$1" | xxd -r -p
}
ascii_to_hex() {
echo -n "$1" | xxd -p
}
print_monitors() {
declare -r us=';' # separator string;
# If EDID has more than one field with same tag, concatenate them,
# but add this string in between.
declare -r fs=$'\x1f' # Field separator for internal use;
# must be a character that does not occur in data fields.
declare -r invalid_edid_tag='--bad EDID--'
# If base EDID is invalid, don't try to extract information from it,
# but assign this string to the fields.
declare -r format="%-12s%-12s%-28s%-28s\n"
# awk option for portability
declare -r awk_option=$(awk -Wversion &>/dev/null && printf -- "-Wposix")
declare OIFS=$IFS; # save old IFS
IFS=$fs
printf "$format" "Output" "Connection" "Name" "Data"
printf '%0.s–' {1..80}; printf '\n'
while read -r output conn hexn hexd; do
printf "$format" "'$output'" "'$conn'" "'$(hex_to_ascii "$hexn")'" "'$(hex_to_ascii "$hexd")'"
done < <(xrandr --prop | awk -v gfs="$fs" $awk_option '
function print_fields() {
print output, conn, hexn, hexd
conn=""; hexn=""; hexd=""
}
function append_hex_field(src_hex,position,app_hex, n) {
n=substr(src_hex,position+10,26)
sub(/0a.*/, "", n)
if (n && app_hex) return app_hex sp n
else return app_hex n
}
function get_hex_edid( hex) {
getline
while (/^[ \t]*[a-f0-9A-F]+$/) {
sub(/[ \t]*/, "")
hex = hex $0
getline
}
return hex
}
function valid_edid(hex, a, sum) {
if (length(hex)<256) return 0
for ( a=1; a<=256; a+=2 ) {
# this requires gawk
#sum+=strtonum("0x" substr(hex,a,2))
# this requires --non-decimal-data or --posix for gawk:
sum+=sprintf("%d", "0x" substr(hex,a,2))
}
if (sum % 256) return 0
return 1
}
BEGIN {
OFS=gfs
}
/^[^ \t]+ connected/ {
if (notfirst) print_fields()
notfirst=1
output=$1
}
/^[ \t]*EDID.*:/ {
hex=get_hex_edid()
if (valid_edid(hex)) {
for ( c=109; c<=217; c+=36 ) {
tag=substr(hex,c,10)
if (tag=="000000fc00") hexn=append_hex_field(hex,c,hexn)
else if (tag=="000000fe00") hexd=append_hex_field(hex,c,hexd)
}
} else {
# set special value to denote invalid EDID
hexn=iet; hexd=iet
}
}
/ConnectorType:/ {
conn=$2
}
END {
print_fields()
}' sp=$(ascii_to_hex $us) iet=$(ascii_to_hex $invalid_edid_tag))
IFS="$OIFS"
}
# if there's no pre-defined monitors list, read from xrandr
# and save them to config file
if [[ -z "$monitors" ]]; then
while read -r output ; do
monitors+=("$output")
done < <(xrandr | awk '$2 ~/^c/{print $1}' | sort)
cat > "$config" <<-EOF
# $myname config file
# List of monitors, from left to right. Edit to your actual layout
monitors=(${monitors[@]})
# Extra xrandr options for each monitor.
# Useful when EDID data does not reflect actual preferred mode
# Options for non-existing outputs (such as the examples below) are ignored
# Examples:
monitor_opts[DFPx]="--mode 1920x1080 --rate 60"
monitor_opts[DFPy]="--mode 1280x720"
# As a reference, these were the connected monitors when this config file was created
# use it as a guide when editing the above monitors list and extra options
$(print_monitors|awk '{print "# " $0}')
# For an updated list, run $myname --list
EOF
fi
message() { printf "%s\n" "$1" >&2 ; }
fatal() { [[ "$1" ]] && message "$myname: error: $1" ; exit ${2:-1} ; }
argerr() { printf "%s: %s\n" "$myname" "${1:-error}" >&2 ; usage 1 ; }
invalid() { argerr "invalid argument: $1" ; }
missing() { argerr "missing ${2:+$2 }operand${1:+ from $1}." ; }
usage() {
cat <<-USAGE
Usage: $myname [options]
USAGE
if [[ "$1" ]] ; then
cat >&2 <<- USAGE
Try '$myname --help' for more information.
USAGE
exit 1
fi
cat <<-USAGE
Switch monitors using xrandr.
Options:
-h|--help - show this page.
-v|--verbose - print in terminal the full xrandr command executed.
-l|--list - list connector and monitor names of connected outputs
-a|--all - enable all monitors.
-s|--select OUTPUT - enable monitor OUTPUT, disable all others.
-l|--left - enable leftmost monitor. Alias for --select ${monitors[0]}
-r|--right - enable rightmost monitor. Alias for --select ${monitors[${#monitors[@]}-1]}
Copyright (C) 2012 Rodrigo Silva (MestreLion) <linux@rodrigosilva.com>
2015 Jarno Suni <8@iki.fi>
License: GPLv3 or later. See <http://www.gnu.org/licenses/gpl.html>
USAGE
exit 0
}
# Option handling
for arg in "$@"; do [[ "$arg" == "-h" || "$arg" == "--help" ]] && usage ; done
while (( $# )); do
case "$1" in
-v|--verbose) verbose=1 ;;
-q|--no-notify) notify=0 ;;
-l|--list) list=1 ;;
-a|--all) all=1 ;;
-s|--select) shift ; monitor="$1" ;;
-l|--left ) monitor="${monitors[0]}" ;;
-r|--right) monitor="${monitors[${#monitors[@]}-1]}" ;;
*) invalid "$1" ;;
esac
shift
done
if ((list)); then
echo "Connected monitors:"
print_monitors
exit
fi
if [[ -z "$monitor" && -z "$all" ]]; then
usage
fi
# Loop outputs (monitors)
for output in "${monitors[@]}"; do
if ((all)) || [[ "$output" = "$monitor" ]]; then
xrandropts+=(--output "$output" --auto ${monitor_opts["$output"]})
if ((all)); then
if [[ "$output" = "${monitors[0]}" ]]; then
xrandropts+=(--pos 0x0 --primary)
else
xrandropts+=(--right-of "$previous")
fi
previous="$output"
else
xrandropts+=(--primary)
fi
else
xrandropts+=(--output "$output" --off)
fi
done
((verbose)) && message "$myname: executing xrandr ${xrandropts[*]}"
xrandr "${xrandropts[@]}"