| 1 | #!/bin/sh |
| 2 | |
| 3 | # This script starts an instance of Xvfb, the "fake" X server, runs a command |
| 4 | # with that server available, and kills the X server when done. The return |
| 5 | # value of the command becomes the return value of this script, except in cases |
| 6 | # where this script encounters an error. |
| 7 | # |
| 8 | # If anyone is using this to build a Debian package, make sure the package |
| 9 | # Build-Depends on xvfb and xauth. |
| 10 | |
| 11 | set -e |
| 12 | |
| 13 | PROGNAME=xvfb-run |
| 14 | SERVERNUM=99 |
| 15 | AUTHFILE= |
| 16 | ERRORFILE=/dev/null |
| 17 | XVFBARGS="-screen 0 640x480x8" |
| 18 | LISTENTCP="-nolisten tcp" |
| 19 | XAUTHPROTO=. |
| 20 | |
| 21 | # Query the terminal to establish a default number of columns to use for |
| 22 | # displaying messages to the user. This is used only as a fallback in the event |
| 23 | # the COLUMNS variable is not set. ($COLUMNS can react to SIGWINCH while the |
| 24 | # script is running, and this cannot, only being calculated once.) |
| 25 | DEFCOLUMNS=$(stty size 2>/dev/null | awk '{print $2}') || true |
| 26 | if ! expr "$DEFCOLUMNS" : "[[:digit:]]\+$" >/dev/null 2>&1; then |
| 27 | DEFCOLUMNS=80 |
| 28 | fi |
| 29 | |
| 30 | # Display a message, wrapping lines at the terminal width. |
| 31 | message () { |
| 32 | echo "$PROGNAME: $*" | fmt -t -w ${COLUMNS:-$DEFCOLUMNS} |
| 33 | } |
| 34 | |
| 35 | # Display an error message. |
| 36 | error () { |
| 37 | message "error: $*" >&2 |
| 38 | } |
| 39 | |
| 40 | # Display a usage message. |
| 41 | usage () { |
| 42 | if [ -n "$*" ]; then |
| 43 | message "usage error: $*" |
| 44 | fi |
| 45 | cat <<EOF |
| 46 | Usage: $PROGNAME [OPTION ...] COMMAND |
| 47 | Run COMMAND (usually an X client) in a virtual X server environment. |
| 48 | Options: |
| 49 | -a --auto-servernum try to get a free server number, starting at |
| 50 | --server-num |
| 51 | -e FILE --error-file=FILE file used to store xauth errors and Xvfb |
| 52 | output (default: $ERRORFILE) |
| 53 | -f FILE --auth-file=FILE file used to store auth cookie |
| 54 | (default: ./.Xauthority) |
| 55 | -h --help display this usage message and exit |
| 56 | -n NUM --server-num=NUM server number to use (default: $SERVERNUM) |
| 57 | -l --listen-tcp enable TCP port listening in the X server |
| 58 | -p PROTO --xauth-protocol=PROTO X authority protocol name to use |
| 59 | (default: xauth command's default) |
| 60 | -s ARGS --server-args=ARGS arguments (other than server number and |
| 61 | "-nolisten tcp") to pass to the Xvfb server |
| 62 | (default: "$XVFBARGS") |
| 63 | EOF |
| 64 | } |
| 65 | |
| 66 | # Find a free server number by looking at .X*-lock files in /tmp. |
| 67 | find_free_servernum() { |
| 68 | # Sadly, the "local" keyword is not POSIX. Leave the next line commented in |
| 69 | # the hope Debian Policy eventually changes to allow it in /bin/sh scripts |
| 70 | # anyway. |
| 71 | #local i |
| 72 | |
| 73 | i=$SERVERNUM |
| 74 | while [ -f /tmp/.X$i-lock ]; do |
| 75 | i=$(($i + 1)) |
| 76 | done |
| 77 | echo $i |
| 78 | } |
| 79 | |
| 80 | # Clean up files |
| 81 | clean_up() { |
| 82 | if [ -e "$AUTHFILE" ]; then |
| 83 | XAUTHORITY=$AUTHFILE xauth remove ":$SERVERNUM" >>"$ERRORFILE" 2>&1 |
| 84 | fi |
| 85 | if [ -n "$XVFB_RUN_TMPDIR" ]; then |
| 86 | if ! rm -r "$XVFB_RUN_TMPDIR"; then |
| 87 | error "problem while cleaning up temporary directory" |
| 88 | exit 5 |
| 89 | fi |
| 90 | fi |
| 91 | if [ -n "$XVFBPID" ]; then |
| 92 | kill "$XVFBPID" >>"$ERRORFILE" 2>&1 |
| 93 | fi |
| 94 | } |
| 95 | |
| 96 | # Parse the command line. |
| 97 | ARGS=$(getopt --options +ae:f:hn:lp:s:w: \ |
| 98 | --long auto-servernum,error-file:,auth-file:,help,server-num:,listen-tcp,xauth-protocol:,server-args:,wait: \ |
| 99 | --name "$PROGNAME" -- "$@") |
| 100 | GETOPT_STATUS=$? |
| 101 | |
| 102 | if [ $GETOPT_STATUS -ne 0 ]; then |
| 103 | error "internal error; getopt exited with status $GETOPT_STATUS" |
| 104 | exit 6 |
| 105 | fi |
| 106 | |
| 107 | eval set -- "$ARGS" |
| 108 | |
| 109 | while :; do |
| 110 | case "$1" in |
| 111 | -a|--auto-servernum) SERVERNUM=$(find_free_servernum); AUTONUM="yes" ;; |
| 112 | -e|--error-file) ERRORFILE="$2"; shift ;; |
| 113 | -f|--auth-file) AUTHFILE="$2"; shift ;; |
| 114 | -h|--help) SHOWHELP="yes" ;; |
| 115 | -n|--server-num) SERVERNUM="$2"; shift ;; |
| 116 | -l|--listen-tcp) LISTENTCP="" ;; |
| 117 | -p|--xauth-protocol) XAUTHPROTO="$2"; shift ;; |
| 118 | -s|--server-args) XVFBARGS="$2"; shift ;; |
| 119 | -w|--wait) shift ;; |
| 120 | --) shift; break ;; |
| 121 | *) error "internal error; getopt permitted \"$1\" unexpectedly" |
| 122 | exit 6 |
| 123 | ;; |
| 124 | esac |
| 125 | shift |
| 126 | done |
| 127 | |
| 128 | if [ "$SHOWHELP" ]; then |
| 129 | usage |
| 130 | exit 0 |
| 131 | fi |
| 132 | |
| 133 | if [ -z "$*" ]; then |
| 134 | usage "need a command to run" >&2 |
| 135 | exit 2 |
| 136 | fi |
| 137 | |
| 138 | if ! which xauth >/dev/null; then |
| 139 | error "xauth command not found" |
| 140 | exit 3 |
| 141 | fi |
| 142 | |
| 143 | # tidy up after ourselves |
| 144 | trap clean_up EXIT |
| 145 | |
| 146 | # If the user did not specify an X authorization file to use, set up a temporary |
| 147 | # directory to house one. |
| 148 | if [ -z "$AUTHFILE" ]; then |
| 149 | XVFB_RUN_TMPDIR="$(mktemp -d -t $PROGNAME.XXXXXX)" |
| 150 | # Create empty file to avoid xauth warning |
| 151 | AUTHFILE=$(tempfile -n "$XVFB_RUN_TMPDIR/Xauthority") |
| 152 | fi |
| 153 | |
| 154 | # Start Xvfb. |
| 155 | MCOOKIE=$(mcookie) |
| 156 | tries=10 |
| 157 | while [ $tries -gt 0 ]; do |
| 158 | tries=$(( $tries - 1 )) |
| 159 | XAUTHORITY=$AUTHFILE xauth source - << EOF >>"$ERRORFILE" 2>&1 |
| 160 | add :$SERVERNUM $XAUTHPROTO $MCOOKIE |
| 161 | EOF |
| 162 | # handle SIGUSR1 so Xvfb knows to send a signal when it's ready to accept |
| 163 | # connections |
| 164 | trap : USR1 |
| 165 | (trap '' USR1; exec Xvfb ":$SERVERNUM" $XVFBARGS $LISTENTCP -auth $AUTHFILE >>"$ERRORFILE" 2>&1) & |
| 166 | XVFBPID=$! |
| 167 | |
| 168 | wait || : |
| 169 | if kill -0 $XVFBPID 2>/dev/null; then |
| 170 | break |
| 171 | elif [ -n "$AUTONUM" ]; then |
| 172 | # The display is in use so try another one (if '-a' was specified). |
| 173 | SERVERNUM=$((SERVERNUM + 1)) |
| 174 | SERVERNUM=$(find_free_servernum) |
| 175 | continue |
| 176 | fi |
| 177 | error "Xvfb failed to start" >&2 |
| 178 | XVFBPID= |
| 179 | exit 1 |
| 180 | done |
| 181 | |
| 182 | # Start the command and save its exit status. |
| 183 | set +e |
| 184 | DISPLAY=:$SERVERNUM XAUTHORITY=$AUTHFILE "$@" 2>&1 |
| 185 | RETVAL=$? |
| 186 | set -e |
| 187 | |
| 188 | # Return the executed command's exit status. |
| 189 | exit $RETVAL |
| 190 | |
| 191 | # vim:set ai et sts=4 sw=4 tw=80: |