#!/bin/bash # # xmonitor [options] [arrangement] [ -- extra_randr_options ] # # Layout monitors in common ways (creating and using xrandr commands). # Turning them on and off as appropriate, and re-arranging them in the # specified way. # # If no arrangement is given it will just list the monitors available. # # The primary monitor is normally the 'built-in' monitor of a laptop, or # primary output of a desktop, with other monitors listed in sequence. # However it isn't always the first, nor what the user may consider to be # primary. As such we ignore that tag completely and only use the order # which xrandr lists monitors for arragement purposes (this may change in # the future). # # Typical Usage... # xmonitor clone # make all monitors identical (good fallback) # xmonitor right # all monitors in a long row, left to right # xmonitor --skip right # ditto, but turn off closed/docked laptop monitor # xmonitor right -- --rotate left # last monitor is rotated 90 degrees # # Options # -n Dry Run - echo randr command # --help This documentation # --skip Skip (turn off) the first monitor, then continue as normal # Unless that is the ONLY monitor available. # # Arrangement # list List ALL the monitors, not just those available # # cycle Enable the next 'off' monitor, in sequence (rest off) # swap Alias for 'cycle' (generally used for two monitors only) # # clone Clone all connected monitors to be identical # first Just enable First Monitor (rest off) # second Just enable Second Monitor (rest off) # right horizontally arrgemnt in order, left to right (all enabled) # left reverse order horizontally (first listed is on right) # above stack monitors above each other vertically (first at bottom) # below place monitors in order vertically top down (first at top) # # clone-right Clone first and second monitors, make all others to right # NB: first monitor remains active, unlike '--skip right' # This may eventually become a seperate option. # # More Arrgements can be added for special cases. # # WARNING: Be careful as monitors that are later 'closed' or turned off, # yet reported as still 'connected' could leave you without a working display. # Though the program tries to ensure at least one monitor is enabled. # # Caution is especially recommended for the 'cycle', 'swap' or 'second' # arrangements. It is suggested that you set "xmonitor clone" or some other # arramgement as a hotkey command, in case your normal monitor is suddenly # no longer available. ### # # PROGRAMMERS NOTES: # This script treats the first monitor listed by xrandr as being the most # important monitor. It ignores any 'primary' tag that xrandr may list. # # This was required, as many desktops do not always mark the most appropriate # monitor as primary. Most laptops mark the built-in display as primary, but # also generally lists them first anyway, even if that screen is closed, and # laptop is docked. # # Anthony Thyssen -- Nov 2015 # ##### # Discover where the shell script resides PROGNAME=`type $0 | awk '{print $3}'` # search for executable on path PROGDIR=`dirname "$PROGNAME"` # extract directory of program PROGNAME=`basename "$PROGNAME"` # base name of program Usage() { # Report error and Synopsis line only echo >&2 "$PROGNAME:" "$@" sed >&2 -n '1,2d; /^###/q; /^#/!q; /^#$/q; s/^# */Usage: /p;' \ "$PROGDIR/$PROGNAME" echo >&2 "For more detailed help use --help." exit 10; } Help() { # Report error and Synopsis line only sed >&2 -n '1d; /^###/q; /^#/!q; s/^#//; s/^ //; p' \ "$PROGDIR/$PROGNAME" exit 10; } # Option and Arrangement... while (( $# > 0 )); do # get option and remove any and all '-' prefixes option=`echo "X$1" | sed 's/^X-*//'` shift case "$option" in help|doc|'?') Help ;; dryrun|n) dryrun="echo" ;; skip|sk|s) skipfirst="true" ;; list|ls|l) list="true" ;; *) ARRANGEMENT="$option"; break ;; esac done if [[ $1 = '--' ]]; then shift; # extra randr options, kept in args elif (( $# > 0 )); then Usage "Too many options" fi # ------------------------------------- # Collect an organise the monitor list # Get the list of monitors all=$(xrandr -q | sed '/connect/!d; s/ (.*//; # ignore uninteresting information s/[0-9]*x[0-9]*/ ON &/; # is monitor ON? / ON /! s/$/ OFF/; # otherwise it must be off s/ primary \(.*\)/ \1 primary/; # move "primary" tag to EOL ' | column -t) # Get list of just the connected monitors, and their ON/OFF state. # The 'state' is only used to 'cycle' or 'swap' between monitors. # All other arrangements is fixed regardless of their current 'state'. monitors=$(sed '/ *connected *\(ON\|OFF\).*/!d; s// \1/' <<< "$all") # collect any disconnected monitors that are ON, and disable them disable=$(sed '/ *disconnected *ON.*/!d; s/ .*//; s/.*/--output & --off/; # could be --auto or --off ' <<< "$all") # List all monitors and there state and location if [[ $list ]]; then echo "$all" exit 0; fi # No arrangment just give a simple list of currently turned on monitors # Lets you easilly get the 'last monitor' for example # EG: xrandr --output $(xmonitor | tail -1) --rotate left if [[ -z $ARRANGEMENT ]]; then awk '{print $1}' <<< "$monitors" exit 0; fi # Debug - list the default monitor order #echo "disable = $disable"; #column -t <<< "$monitors"; echo "------"; exit # SPECIAL CASE: We have only one monitor... # Enable the single one that is on, and disable the others if (( $(wc -l <<< "$monitors") < 2 )); then [ "$dryrun" ] && echo "# Only ONE connected monitor, ensure it is enabled!" $dryrun xrandr $disable \ --output ${monitors%% *} --auto --pos 0x0 "$@" exit 0 fi # Add first monitor to 'disable' list, before arranging others as requested. # This is typically the built-in monitor of a closed and docked laptop. if [[ $skipfirst ]]; then disable="$disable --output $(sed -n '1{s/ .*//;p;q;}' <<< "$monitors") --off" monitors=$(tail -n+2 <<< "$monitors") fi # Debug - list the default monitor order #echo "disable = $disable"; #column -t <<< "$monitors"; echo "------"; exit # Handle 'cycling' arragement, turning on connected monitors in sequence. # Or re-order the list for later arrangements. # Remove the 'on/off' status of monitors to simply. case "$ARRANGEMENT" in cycle|swap) # Cycle though the connected monitors (make next monitor active). Look for # the current active monitor, and move the next off monitor to the start of # the list so as to make it the 'first' monitor. # # If no 'off' monitor is found after an 'on' monitor, turn on first monitor. # # NOTE: This can handle a single monitor, but that is handled above # as special case. # monitors=$( awk ' # Note the first monitor, as fallback, if we fail to find one off. NR==1 { fallback=$1 } # If the first is already on, make the second monitor the fallback # regardless of if it is on or off. NR==2 { if ( monitor[fallback] ) fallback=$1 } # If we have not found the next monitor, # and the last monitor is on and this monitor is off # mark it as the monitor to turn on. { monitor[$1]=($2=="ON"); # record if monitor is on or off if ( NR>1 && !turnon && !monitor[$1] && monitor[prev] ) turnon=$1; prev=$1 # note the previous monitor } # If no monitor was off with the previous monitor on, use fallback. # Output the selected monitor as the first one on the list. END { if ( !turnon ) turnon=fallback # no monitor to turnon - use fallback print turnon # put monitor to turn, the first monitor in list # then list all the other monitors so they can be turned off for (m in monitor) if ( m != turnon && monitor[m] ) print m; }' <<< "$monitors" ) ARRANGEMENT=first # turn on first monitor in list, and turn off all others ;; second*) # Swap the first two monitors, to make the second monitor, the first monitor # Remove the on/off status in the list, as it is no longer needed. # NOTE: This can handle a single monitor, but handled above as special case monitors=$( awk 'NR==1 { first=$1 } # note first primary monitor NR==2 { print $1; print first } # swap the first two entries NR>2 { print $1 } # print the rest END { if ( NR==1 ) print first} # only first monitor present! ' <<< "$monitors" ) ARRANGEMENT=first # turn on first monitor in list, and turn off all others ;; *) # Just prepare a list of the all available monitors in ORIGINAL order. # Remove the on/off status in the list, as it is no longer needed. monitors=$(awk '{print $1}' <<< "$monitors" ) esac # Debug - list the monitor order #column -t <<< "$monitors"; echo "------" # -------- # Seperate list into first monitor and the rest first=`echo $monitors | awk '{print $1}'` # first monitor in list rest=`echo $monitors | sed 's/^[^ ]* *//'` # rest of the monitors # ------------------------------------- # Now we can do the job, in various ways case "$ARRANGEMENT" in clone) # Clone the same display to ALL monitors $dryrun xrandr $disable \ $(for m in $monitors; do echo "--output $m --auto --pos 0x0" done ) "$@" ;; first) # Turn on the first monitor on list - turn off the rest $dryrun xrandr $disable \ --output $first --auto --pos 0x0 \ $(for m in $rest; do echo "--output $m --off" done ) "$@" ;; right) # horizontally left to right (all enabled) $dryrun xrandr $disable \ --output $first --auto --pos 0x0 \ $(for m in $rest; do echo "--output $m --auto --right-of $first" first=$m done ) "$@" ;; clone-right) # clone first and second, rest to the right second=`echo $rest | awk '{print $1}'` # get second monitor rest=`echo $rest | sed 's/^[^ ]* *//'` # thrid and later monitors (if any) $dryrun xrandr $disable \ --output $first --auto --pos 0x0 \ --output $second --auto --pos 0x0 \ $(for m in $rest; do echo "--output $m --auto --right-of $second" second=$m done ) "$@" ;; left) # reverse order horizontally (first on right) $dryrun xrandr $disable \ --output $first --auto --pos 0x0 \ $(for m in $rest; do echo "--output $m --auto --left-of $first" first=$m done ) "$@" ;; above) # stack monitors vertically with first at bottom $dryrun xrandr #disable \ --output $first --auto --pos 0x0 \ $(for m in $rest; do echo "--output $m --auto --above $first" first=$m done ) "$@" ;; below) # place monitors in order vertically top down $dryrun xrandr $disable \ --output $first --auto --pos 0x0 \ $(for m in $rest; do echo "--output $m --auto --below $first" first=$m done ) "$@" ;; *) Usage "Unknown arragment of monitors: \"$ARRANGEMENT\"" exit 10 ;; esac # ------------------------------------------------- # Finish up by resetting synergy (server or client) (if running) read synergy synergy_args < <(ps -C synergys -C synergyc -o args=) if [ "X$synergy" != X ]; then $dryrun killall $synergy $dryrun $synergy $synergy_args fi exit 0 # -------------------------------------------------