Merge branch 'master' of ssh://git.slaskete.net/srv/git/einar-bin
authorEinar Jørgen Haraldseid <einar@haraldseid.net>
Mon, 17 Feb 2014 12:16:02 +0000 (13:16 +0100)
committerEinar Jørgen Haraldseid <einar@haraldseid.net>
Mon, 17 Feb 2014 12:16:02 +0000 (13:16 +0100)
efnet
prettyping.sh [new file with mode: 0755]
screencast.sh

diff --git a/efnet b/efnet
index 632177bf1343a09af75aa19003690a5018f4dbd8..a19567dae17ff52a145f52d9d1ccf48a08634ace 100755 (executable)
--- a/efnet
+++ b/efnet
@@ -1,4 +1,4 @@
 #!/bin/bash
 #server=slartibartfast.slaskete.net
 server=eddie.slaskete.net
-ssh -t $server 'screen -dr irssi'
+TERM=xterm-256color mosh $server  -- screen -drU weechat
diff --git a/prettyping.sh b/prettyping.sh
new file mode 100755 (executable)
index 0000000..aa4e845
--- /dev/null
@@ -0,0 +1,655 @@
+#!/bin/bash
+#
+# Written by Denilson Figueiredo de Sá <denilsonsa@gmail.com>
+# MIT license
+#
+# Requirements:
+# * bash (tested on 4.20, should work on older versions too)
+# * gawk (GNU awk, tested on 4.0.1, should work on older versions too)
+# * ping (from iputils)
+
+# TODO: Detect the following kind of message and avoid printing it repeatedly.
+# From 192.168.1.11: icmp_seq=4 Destination Host Unreachable
+#
+# TODO: print the destination (also) at the bottom bar. Useful after leaving
+# the script running for quite some time.
+#
+# TODO: Implement audible ping.
+#
+# TODO: Autodetect the width of printf numbers, so they will always line up correctly.
+#
+# TODO: Test the behavior of this script upon receiving out-of-order packets, like these:
+#   http://www.blug.linux.no/rfc1149/pinglogg.txt
+#
+# TODO? How will prettyping behave if it receives a duplicate response?
+
+print_help() {
+       cat << EOF
+Usage: $MYNAME [prettyping parameters] <standard ping parameters>
+
+This script is a wrapper around the system's "ping" tool. It will substitute
+each ping response line by a colored character, giving a very compact overview
+of the ping responses.
+
+prettyping parameters:
+  --[no]color      Enable/disable color output. (default: enabled)
+  --[no]multicolor Enable/disable multi-color unicode output. Has no effect if
+                     either color or unicode is disabled. (default: enabled)
+  --[no]unicode    Enable/disable unicode characters. (default: enabled)
+  --[no]terminal   Force the output designed to a terminal. (default: auto)
+  --last <n>       Use the last "n" pings at the statistics line. (default: 60)
+  --columns <n>    Override auto-detection of terminal dimensions.
+  --lines <n>      Override auto-detection of terminal dimensions.
+  --rttmin <n>     Minimum RTT represented in the unicode graph. (default: auto)
+  --rttmax <n>     Maximum RTT represented in the unicode graph. (default: auto)
+
+ping parameters handled by prettyping:
+  -a  Audible ping is not implemented yet.
+  -f  Flood mode is not allowed in prettyping.
+  -q  Quiet output is not allowed in prettyping.
+  -R  Record route mode is not allowed in prettyping.
+  -v  Verbose output seems to be the default mode in ping.
+
+Tested with Linux ping tool from "iputils" package:
+http://www.linuxfoundation.org/collaborate/workgroups/networking/iputils
+EOF
+}
+
+# Thanks to people at #bash who pointed me at
+# http://bash-hackers.org/wiki/doku.php/scripting/posparams
+parse_arguments() {
+       USE_COLOR=1
+       USE_MULTICOLOR=1
+       USE_UNICODE=1
+
+       if [ -t 1 ]; then
+               IS_TERMINAL=1
+       else
+               IS_TERMINAL=0
+       fi
+
+       LAST_N=60
+       OVERRIDE_COLUMNS=0
+       OVERRIDE_LINES=0
+       RTT_MIN=auto
+       RTT_MAX=auto
+
+       PING_PARAMS=( )
+
+       while [[ $# != 0 ]] ; do
+               case "$1" in
+                       -h | -help | --help )
+                               print_help
+                               exit
+                               ;;
+
+                       # Forbidden ping parameters within prettyping:
+                       -f )
+                               echo "${MYNAME}: You can't use the -f (flood) option."
+                               exit 1
+                               ;;
+                       -R )
+                               # -R prints extra information at each ping response.
+                               echo "${MYNAME}: You can't use the -R (record route) option."
+                               exit 1
+                               ;;
+                       -q )
+                               echo "${MYNAME}: You can't use the -q (quiet) option."
+                               exit 1
+                               ;;
+                       -v )
+                               # -v enables verbose output. However, it seems the output with
+                               # or without this option is the same. Anyway, prettyping will
+                               # strip this parameter.
+                               ;;
+                       # Note:
+                       #  Small values for -s parameter prevents ping from being able to
+                       #  calculate RTT.
+
+                       # New parameters:
+                       -a )
+                               # TODO: Implement audible ping for responses or for missing packets
+                               ;;
+
+                       -color        | --color        ) USE_COLOR=1 ;;
+                       -nocolor      | --nocolor      ) USE_COLOR=0 ;;
+                       -multicolor   | --multicolor   ) USE_MULTICOLOR=1 ;;
+                       -nomulticolor | --nomulticolor ) USE_MULTICOLOR=0 ;;
+                       -unicode      | --unicode      ) USE_UNICODE=1 ;;
+                       -nounicode    | --nounicode    ) USE_UNICODE=0 ;;
+                       -terminal     | --terminal     ) IS_TERMINAL=1 ;;
+                       -noterminal   | --noterminal   ) IS_TERMINAL=0 ;;
+
+                       #TODO: Check if these parameters are numbers.
+                       -last    | --last    ) LAST_N="$2"           ; shift ;;
+                       -columns | --columns ) OVERRIDE_COLUMNS="$2" ; shift ;;
+                       -lines   | --lines   ) OVERRIDE_LINES="$2"   ; shift ;;
+                       -rttmin  | --rttmin  ) RTT_MIN="$2"          ; shift ;;
+                       -rttmax  | --rttmax  ) RTT_MAX="$2"          ; shift ;;
+
+                       * )
+                               PING_PARAMS+=("$1")
+                               ;;
+               esac
+               shift
+       done
+
+       if [[ "${RTT_MIN}" -gt 0 && "${RTT_MAX}" -gt 0 && "${RTT_MIN}" -ge "${RTT_MAX}" ]] ; then
+               echo "${MYNAME}: Invalid --rttmin and -rttmax values."
+               exit 1
+       fi
+
+       if [[ "${#PING_PARAMS[@]}" = 0 ]] ; then
+               echo "${MYNAME}: Missing parameters, use --help for instructions."
+               exit 1
+       fi
+}
+
+MYNAME=`basename "$0"`
+parse_arguments "$@"
+
+
+export LC_ALL=C
+
+# Warning! Ugly code ahead!
+# The code is so ugly that the comments explaining it are
+# bigger than the code itself!
+#
+# Suppose this:
+#
+#   cmd_a | cmd_b &
+#
+# I need the PID of cmd_a. How can I get it?
+# In bash, $! will give me the PID of cmd_b.
+#
+# So, I came up with this ugly solution: open a subshell, like this:
+#
+# (
+#      cmd_a &
+#      echo "This is the PID I want $!"
+#      wait
+# ) | cmd_b
+
+
+# Ignore Ctrl+C here.
+# If I don't do this, this shell script is killed before
+# ping and gawk can finish their work.
+trap '' 2
+
+# Now the ugly code.
+(
+       ping "${PING_PARAMS[@]}" &
+       # Commented out, because it looks like this line is not needed
+       #trap "kill -2 $! ; exit 1" 2  # Catch Ctrl+C here
+       wait
+) 2>&1 | gawk '
+# Weird that awk does not come with abs(), so I need to implement it.
+function abs(x) {
+       return ( (x < 0) ? -x : x )
+}
+
+# Ditto for ceiling function.
+function ceil(x) {
+       return (x == int(x)) ? x : int(x) + 1
+}
+
+# Currently, this function is called once, at the beginning of this
+# script, but it is also possible to call this more than once, to
+# handle window size changes while this program is running.
+#
+# Local variables MUST be declared in argument list, else they are
+# seen as global. Ugly, but that is how awk works.
+function get_terminal_size(SIZE,SIZEA) {
+       if( HAS_STTY ) {
+               if( (STTY_CMD | getline SIZE) == 1 ) {
+                       split(SIZE, SIZEA, " ")
+                       LINES   = SIZEA[1]
+                       COLUMNS = SIZEA[2]
+               } else {
+                       HAS_STTY = 0
+               }
+               close(STTY_CMD)
+       }
+       if ( int('"${OVERRIDE_COLUMNS}"') ) { COLUMNS = int('"${OVERRIDE_COLUMNS}"') }
+       if ( int('"${OVERRIDE_LINES}"')   ) { LINES   = int('"${OVERRIDE_LINES}"')   }
+}
+
+############################################################
+# Functions related to cursor handling
+
+# Function called whenever a non-dotted line is printed.
+#
+# It will move the cursor to the line next to the statistics and
+# restore the default color.
+function other_line_is_printed() {
+       if( IS_PRINTING_DOTS ) {
+               if( '"${IS_TERMINAL}"' ) {
+                       printf( ESC_DEFAULT ESC_NEXTLINE ESC_NEXTLINE "\n" )
+               } else {
+                       printf( ESC_DEFAULT "\n" )
+                       print_statistics_bar()
+               }
+       }
+       IS_PRINTING_DOTS = 0
+       CURR_COL = 0
+}
+
+# Function called whenever a non-dotted line is repeated.
+function other_line_is_repeated() {
+       if (other_line_times < 2) {
+               return
+       }
+       if( '"${IS_TERMINAL}"' ) {
+               printf( ESC_DEFAULT ESC_ERASELINE "\r" )
+       }
+       printf( "Last message repeated %d times.", other_line_times )
+       if( ! '"${IS_TERMINAL}"' ) {
+               printf( "\n" )
+       }
+}
+
+# Prints the newlines required for the live statistics.
+#
+# I need to print some newlines and then return the cursor back to its position
+# to make sure the terminal will scroll.
+#
+# If the output is not a terminal, break lines on every LAST_N dots.
+function print_newlines_if_needed() {
+       if( '"${IS_TERMINAL}"' ) {
+               # COLUMNS-1 because I want to avoid bugs with the cursor at the last column
+               if( CURR_COL >= COLUMNS-1 ) {
+                       CURR_COL = 0
+               }
+               if( CURR_COL == 0 ) {
+                       if( IS_PRINTING_DOTS ) {
+                               printf( "\n" )
+                       }
+                       #printf( "\n" "\n" ESC_PREVLINE ESC_PREVLINE ESC_ERASELINE )
+                       printf( ESC_DEFAULT "\n" "\n" ESC_CURSORUP ESC_CURSORUP ESC_ERASELINE )
+               }
+       } else {
+               if( CURR_COL >= LAST_N ) {
+                       CURR_COL = 0
+                       printf( ESC_DEFAULT "\n" )
+                       print_statistics_bar()
+               }
+       }
+       CURR_COL++
+       IS_PRINTING_DOTS = 1
+}
+
+############################################################
+# Functions related to the data structure of "Last N" statistics.
+
+# Clears the data structure.
+function clear(d) {
+       d["index"] = 0  # The next position to store a value
+       d["size"]  = 0  # The array size, goes up to LAST_N
+}
+
+# This function stores the value to the passed data structure.
+# The data structure holds at most LAST_N values. When it is full,
+# a new value overwrite the oldest one.
+function store(d, value) {
+       d[d["index"]] = value
+       d["index"]++
+       if( d["index"] >= d["size"] ) {
+               if( d["size"] < LAST_N ) {
+                       d["size"]++
+               } else {
+                       d["index"] = 0
+               }
+       }
+}
+
+############################################################
+# Functions related to processing the received response
+
+function process_rtt(rtt) {
+       # Overall statistics
+       last_rtt = rtt
+       total_rtt += rtt
+       if( last_seq == 0 ) {
+               min_rtt = max_rtt = rtt
+       } else {
+               if( rtt < min_rtt ) min_rtt = rtt
+               if( rtt > max_rtt ) max_rtt = rtt
+       }
+
+       # "Last N" statistics
+       store(lastn_rtt,rtt)
+}
+
+############################################################
+# Functions related to printing the fancy ping response
+
+# block_index is just a local variable.
+function print_response_legend(i) {
+       if( '"${USE_UNICODE}"' ) {
+               printf( BLOCK[0] ESC_DEFAULT "%4d ", 0)
+               for( i=1 ; i<BLOCK_LEN ; i++ ) {
+                       printf( BLOCK[i] ESC_DEFAULT "%4d ",
+                               BLOCK_RTT_MIN + ceil((i-1) * BLOCK_RTT_RANGE / (BLOCK_LEN - 2)) )
+               }
+               printf( "\n" )
+       }
+
+       # Useful code for debugging.
+       #for( i=0 ; i<=BLOCK_RTT_MAX ; i++ ) {
+       #       print_received_response(i)
+       #       printf( ESC_DEFAULT "%4d\n", i )
+       #}
+}
+
+# block_index is just a local variable.
+function print_received_response(rtt, block_index) {
+       if( '"${USE_UNICODE}"' ) {
+               if( rtt < BLOCK_RTT_MIN ) {
+                       block_index = 0
+               } else if( rtt >= BLOCK_RTT_MAX ) {
+                       block_index = BLOCK_LEN - 1
+               } else {
+                       block_index = 1 + int((rtt - BLOCK_RTT_MIN) * (BLOCK_LEN - 2) / BLOCK_RTT_RANGE)
+               }
+               printf( BLOCK[block_index] )
+       } else {
+               printf( ESC_GREEN "." )
+       }
+}
+
+function print_missing_response(rtt) {
+       printf( ESC_RED "!" )
+}
+
+############################################################
+# Functions related to printing statistics
+
+function print_overall() {
+       if( '"${IS_TERMINAL}"' ) {
+               printf( "%2d/%3d (%2d%%) lost; %4.0f/" ESC_BOLD "%4.0f" ESC_DEFAULT "/%4.0fms; last: " ESC_BOLD "%4.0f" ESC_DEFAULT "ms",
+                       lost,
+                       lost+received,
+                       (lost*100/(lost+received)),
+                       min_rtt,
+                       (total_rtt/received),
+                       max_rtt,
+                       last_rtt )
+       } else {
+               printf( "%2d/%3d (%2d%%) lost; %4.0f/" ESC_BOLD "%4.0f" ESC_DEFAULT "/%4.0fms",
+                       lost,
+                       lost+received,
+                       (lost*100/(lost+received)),
+                       min_rtt,
+                       (total_rtt/received),
+                       max_rtt )
+       }
+}
+
+function print_last_n(i, sum, min, avg, max, diffs) {
+       # Calculate and print the lost packets statistics
+       sum = 0
+       for( i=0 ; i<lastn_lost["size"] ; i++ ) {
+               sum += lastn_lost[i]
+       }
+       printf( "%2d/%3d (%2d%%) lost; ",
+               sum,
+               lastn_lost["size"],
+               sum*100/lastn_lost["size"] )
+
+       # Calculate the min/avg/max rtt times
+       sum = diffs = 0
+       min = max = lastn_rtt[0]
+       for( i=0 ; i<lastn_rtt["size"] ; i++ ) {
+               sum += lastn_rtt[i]
+               if( lastn_rtt[i] < min ) min = lastn_rtt[i]
+               if( lastn_rtt[i] > max ) max = lastn_rtt[i]
+       }
+       avg = sum/lastn_rtt["size"]
+
+       # Calculate mdev (mean absolute deviation)
+       for( i=0 ; i<lastn_rtt["size"] ; i++ ) {
+               diffs += abs(lastn_rtt[i] - avg)
+       }
+       diffs /= lastn_rtt["size"]
+
+       # Print the rtt statistics
+       printf( "%4.0f/" ESC_BOLD "%4.0f" ESC_DEFAULT "/%4.0f/%4.0fms (last %d)",
+               min,
+               avg,
+               max,
+               diffs,
+               lastn_rtt["size"] )
+}
+
+function print_statistics_bar() {
+       if( '"${IS_TERMINAL}"' ) {
+               printf( ESC_SAVEPOS ESC_DEFAULT )
+
+               printf( ESC_NEXTLINE ESC_ERASELINE )
+               print_overall()
+               printf( ESC_NEXTLINE ESC_ERASELINE )
+               print_last_n()
+
+               printf( ESC_UNSAVEPOS )
+       } else {
+               print_overall()
+               printf( "\n" )
+               print_last_n()
+               printf( "\n" )
+       }
+}
+
+############################################################
+# Initializations
+BEGIN {
+       # Easy way to get each value from ping output
+       FS = "="
+
+       ############################################################
+       # General internal variables
+
+       # This is needed to keep track of lost packets
+       last_seq = 0
+
+       # The previously printed non-ping-response line
+       other_line = ""
+       other_line_times = 0
+
+       # Variables to keep the screen clean
+       IS_PRINTING_DOTS = 0
+       CURR_COL = 0
+
+       ############################################################
+       # Variables related to "overall" statistics
+       received = 0
+       lost = 0
+       total_rtt = 0
+       min_rtt = 0
+       max_rtt = 0
+       last_rtt = 0
+
+       ############################################################
+       # Variables related to "last N" statistics
+       LAST_N = int('"${LAST_N}"')
+
+       # Data structures for the "last N" statistics
+       clear(lastn_lost)
+       clear(lastn_rtt)
+
+       ############################################################
+       # Terminal height and width 
+
+       # These are sane defaults, in case we cannot query the actual terminal size
+       LINES    = 24
+       COLUMNS  = 80
+
+       # Auto-detecting the terminal size
+       HAS_STTY = 1
+       STTY_CMD = "stty size --file=/dev/tty 2> /dev/null"
+       get_terminal_size()
+       if( '"${IS_TERMINAL}"' && COLUMNS <= 50 ) {
+               print "Warning: terminal width is too small."
+       }
+
+       ############################################################
+       # ANSI escape codes
+
+       # Color escape codes.
+       # Fortunately, awk defaults any unassigned variable to an empty string.
+       if( '"${USE_COLOR}"' ) {
+               ESC_DEFAULT = "\033[0m"
+               ESC_BOLD    = "\033[1m"
+               #ESC_BLACK   = "\033[0;30m"
+               #ESC_GRAY    = "\033[1;30m"
+               ESC_RED     = "\033[0;31m"
+               ESC_GREEN   = "\033[0;32m"
+               ESC_YELLOW  = "\033[0;33m"
+               ESC_BLUE    = "\033[0;34m"
+               ESC_MAGENTA = "\033[0;35m"
+               ESC_CYAN    = "\033[0;36m"
+               ESC_WHITE   = "\033[0;37m"
+               ESC_YELLOW_ON_GREEN = "\033[42;33m"
+               ESC_RED_ON_YELLOW   = "\033[43;31m"
+       }
+       # Other escape codes, see:
+       # http://en.wikipedia.org/wiki/ANSI_escape_code
+       # http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+       ESC_NEXTLINE   = "\n"
+       ESC_CURSORUP   = "\033[A"
+       ESC_SCROLLUP   = "\033[S"
+       ESC_SCROLLDOWN = "\033[T"
+       ESC_ERASELINE  = "\033[2K"
+       ESC_SAVEPOS    = "\0337"
+       ESC_UNSAVEPOS  = "\0338"
+
+       # I am avoiding these escapes as they are not listed in:
+       # http://vt100.net/docs/vt100-ug/chapter3.html
+       #ESC_PREVLINE   = "\033[F"
+       #ESC_SAVEPOS    = "\033[s"
+       #ESC_UNSAVEPOS  = "\033[u"
+
+       # I am avoiding this to improve compatibility with (older versions of) tmux
+       #ESC_NEXTLINE   = "\033[E"
+
+       ############################################################
+       # Unicode characters (based on https://github.com/holman/spark )
+       if( '"${USE_UNICODE}"' ) {
+               BLOCK[ 0] = ESC_GREEN "▁"
+               BLOCK[ 1] = ESC_GREEN "▂"
+               BLOCK[ 2] = ESC_GREEN "▃"
+               BLOCK[ 3] = ESC_GREEN "▄"
+               BLOCK[ 4] = ESC_GREEN "▅"
+               BLOCK[ 5] = ESC_GREEN "▆"
+               BLOCK[ 6] = ESC_GREEN "▇"
+               BLOCK[ 7] = ESC_GREEN "█"
+               BLOCK[ 8] = ESC_YELLOW_ON_GREEN "▁"
+               BLOCK[ 9] = ESC_YELLOW_ON_GREEN "▂"
+               BLOCK[10] = ESC_YELLOW_ON_GREEN "▃"
+               BLOCK[11] = ESC_YELLOW_ON_GREEN "▄"
+               BLOCK[12] = ESC_YELLOW_ON_GREEN "▅"
+               BLOCK[13] = ESC_YELLOW_ON_GREEN "▆"
+               BLOCK[14] = ESC_YELLOW_ON_GREEN "▇"
+               BLOCK[15] = ESC_YELLOW_ON_GREEN "█"
+               BLOCK[16] = ESC_RED_ON_YELLOW "▁"
+               BLOCK[17] = ESC_RED_ON_YELLOW "▂"
+               BLOCK[18] = ESC_RED_ON_YELLOW "▃"
+               BLOCK[19] = ESC_RED_ON_YELLOW "▄"
+               BLOCK[20] = ESC_RED_ON_YELLOW "▅"
+               BLOCK[21] = ESC_RED_ON_YELLOW "▆"
+               BLOCK[22] = ESC_RED_ON_YELLOW "▇"
+               BLOCK[23] = ESC_RED_ON_YELLOW "█"
+               if( '"${USE_MULTICOLOR}"' && '"${USE_COLOR}"' ) {
+                       # Multi-color version:
+                       BLOCK_LEN = 24
+                       BLOCK_RTT_MIN = 10
+                       BLOCK_RTT_MAX = 230
+               } else {
+                       # Simple version:
+                       BLOCK_LEN = 8
+                       BLOCK_RTT_MIN = 25
+                       BLOCK_RTT_MAX = 175
+               }
+
+               if( int('"${RTT_MIN}"') > 0 && int('"${RTT_MAX}"') > 0 ) {
+                       BLOCK_RTT_MIN = int('"${RTT_MIN}"')
+                       BLOCK_RTT_MAX = int('"${RTT_MAX}"')
+               } else if( int('"${RTT_MIN}"') > 0 ) {
+                       BLOCK_RTT_MIN = int('"${RTT_MIN}"')
+                       BLOCK_RTT_MAX = BLOCK_RTT_MIN * (BLOCK_LEN - 1)
+               } else if( int('"${RTT_MAX}"') > 0 ) {
+                       BLOCK_RTT_MAX = int('"${RTT_MAX}"')
+                       BLOCK_RTT_MIN = int(BLOCK_RTT_MAX / (BLOCK_LEN - 1))
+               }
+
+               BLOCK_RTT_RANGE = BLOCK_RTT_MAX - BLOCK_RTT_MIN
+               print_response_legend()
+       }
+}
+
+############################################################
+# Main loop
+{
+       # Sample line:
+       # 64 bytes from 8.8.8.8: icmp_seq=1 ttl=49 time=184 ms
+       if( $0 ~ /^[0-9]+ bytes from .*: icmp_[rs]eq=[0-9]+ ttl=[0-9]+ time=[0-9.]+ *ms/ ) {
+               if( other_line_times >= 2 ) {
+                       if( '"${IS_TERMINAL}"' ) {
+                               printf( "\n" )
+                       } else {
+                               other_line_is_repeated()
+                       }
+               }
+               other_line = ""
+               other_line_times = 0
+
+               # $1 = useless prefix string
+               # $2 = icmp_seq
+               # $3 = ttl
+               # $4 = time
+
+               # This must be called before incrementing the last_seq variable!
+               rtt = int($4)
+               process_rtt(rtt)
+
+               seq = int($2)
+
+               while( last_seq < seq - 1 ) {
+                       # Lost a packet
+                       print_newlines_if_needed()
+                       print_missing_response()
+
+                       last_seq++
+                       lost++
+                       store(lastn_lost, 1)
+               }
+
+               # Received a packet
+               print_newlines_if_needed()
+               print_received_response(rtt)
+
+               last_seq++
+               received++
+               store(lastn_lost, 0)
+
+               if( '"${IS_TERMINAL}"' ) {
+                       print_statistics_bar()
+               }
+       } else if ( $0 == "" ) {
+               # Do nothing on blank lines.
+       } else {
+               other_line_is_printed()
+               if ( $0 == other_line ) {
+                       other_line_times++
+                       if( '"${IS_TERMINAL}"' ) {
+                               other_line_is_repeated()
+                       }
+               } else {
+                       other_line = $0
+                       other_line_times = 1
+                       printf( "%s\n", $0 )
+               }
+       }
+
+       # Not needed when the output is a terminal, but does not hurt either.
+       fflush()
+}'
index 4520980cca64357042ee37eae9d9037f5e33a6f8..40a204d6bf5f3d530122f107eeb9b75d612446ec 100755 (executable)
@@ -1,27 +1,43 @@
 #!/bin/bash
-# WARNING: This is a terribly dirty and messy shellscript, written over a couple of late nights.
-# There was alcohol …
-# You probably need to install all the gstreamer-plugins (at least -ugly and -ffmpeg) to get this
-# to work. I wanted to make mp4 with x264, I didn't bother with free codecs.
-# 
-# So this script allows you to create screencasts on Linux, with optional sound inputs (both Mic 
-# and system sounds). It stores system sounds and mic in separate audio streams.
-# You also get to choose to record from the whole screen or just a specified window. It has only 
-# been tested on Fedora 18.
-# 
-# It dumps the recording in your $HOME directory with filename screencast-YYYY-MM-DD-HH-MM-SS.mp4
+
+# WARNING: This is a terribly dirty and messy shellscript, written over a
+# couple of late nights. There was alcohol …
+# You probably need to install all the gstreamer-plugins (at least -ugly
+# and -ffmpeg) as well as gstreamer-tools to get this to work. I wanted to
+# make mp4 files with x264, I didn't bother with free codecs.
+#
+# So this script allows you to create screencasts on Linux, with optional
+# sound inputs (both Microphone and system sounds). It stores system sounds
+# and microphone in separate audio streams.
+#
+# You also get to choose to record from the whole screen or just a specified
+# window. It has been tested on Fedora 18, Ubuntu 13.04 and CrunchBang Waldorf
+# (and thus should work on Debian Wheezy as well).
+#
+# If you're using GStreamer 1.0, please jump to the bottom of the script
+# and comment out the gst-launch-0.10 pipeline and uncomment the 1.0 pipeline.
+#
+# It dumps the recording in your $HOME directory with a filename like
+# screencast-YYYY-MM-DD-HH-MM-SS.mp4
 #
 # Written by Einar Jørgen Haraldseid (http://einar.slaskete.net)
 # License: http://sam.zoy.org/wtfpl/COPYING
 
-# Get device names and pretty names for playback sinks:
-PLAYSINKLIST=$(pacmd list-sinks | grep -e "name: " -e "device.description = " | cut -d " " -f "2-" | sed -e "s/name: \|= //g" -e "s/<\|>\|\x22//g")
+# Get device names and pretty names for enumerating playback sinks:
+PLAYSINKLIST=$(pacmd list-sinks | \
+ grep -e "name: " -e "device.description = " | cut -d " " -f "2-" | \
+ sed -e "s/name: \|= //g" -e "s/<\|>\|\x22//g")
 
 # Display playback monitor chooser
 PLAYMON=$(echo "${PLAYSINKLIST}
 none
-Don't capture system sounds" | zenity --list --title "Choose Output Device" --text "Choose a sound device to capture system sound from:" --column "device" --column "Name" --print-column=1 --hide-column=1 2>/dev/null)
+Don't capture system sounds" | \
+ zenity --list --title "Choose Output Device" \
+ --text "Choose a sound device to capture system sound from:" \
+ --column "device" --column "Name" --print-column=1 \
+ --hide-column=1 2>/dev/null)
 
+# Catch cancel
 if [ -z ${PLAYMON} ]; then
   echo "No choice made on output device, assuming cancel."
   exit 1
@@ -30,28 +46,47 @@ fi
 if [ ${PLAYMON} != "none" ]; then
   # Unmute monitor of the playback sink (if set):
   PLAYMON="${PLAYMON}.monitor"
-
   pacmd set-source-mute ${PLAYMON} false >/dev/null
+  echo "Recording system sounds from ${PLAYMON}"
+else
+  echo "Not recording system sounds."
 fi
 
 # Get device names and pretty names for microphones:
-MICLIST=$(pacmd list-sources | grep -e "name: " -e "device.description = " | grep -v -i "monitor" | cut -d " " -f "2-" | sed -e "s/name: \|= //g" -e "s/<\|>\|\x22//g")
+MICLIST=$(pacmd list-sources | \
+ grep -e "name: " -e "device.description = " | \
+ grep -v -i "monitor" | cut -d " " -f "2-" | \
+ sed -e "s/name: \|= //g" -e "s/<\|>\|\x22//g")
 
 # Display device chooser
 MIC=$(echo "${MICLIST}
 none
-Don't use a microphone" | zenity --list --title "Choose Microphone" --text "Choose a microphone to capture voice from:" --column "device" --column "Name" --print-column=1 --hide-column=1 2>/dev/null)
+Don't use a microphone" | \
+ zenity --list --title "Choose Microphone" \
+ --text "Choose a microphone to capture voice from:" \
+ --column "device" --column "Name" --print-column=1 \
+ --hide-column=1 2>/dev/null)
 
 if [ -z ${MIC} ]; then
   echo "No choice made on microphone, assuming cancel."
   exit 1
-fi 
+fi
+
+if [ ${MIC} != "none" ]; then
+  echo "Recording voice from ${MIC}"
+else
+  echo "Not recording voice."
+fi
 
 # Get target window for recording:
 TARGET=$(echo "root
 Whole screen
 window
-Specific window" | zenity --list --title "Choose recording mode" --text "Do you want to record the whole screen, or record a specific window?" --column "target" --column "Mode" --print-column=1 --hide-column=1 2>/dev/null)
+Specific window" | \
+ zenity --list --title "Choose recording mode" \
+ --text "Do you want to record the whole screen,\
+ or record a specific window?" --column "target" \
+ --column "Mode" --print-column=1 --hide-column=1 2>/dev/null)
 
 if [ -z ${TARGET} ]; then
   echo "No choice for recording target, assuming cancel."
@@ -59,35 +94,50 @@ if [ -z ${TARGET} ]; then
 fi
 
 if [ ${TARGET} = "root" ]; then
-  echo "Root chosen"
+  echo "Root window chosen."
   XWININFO=$(xwininfo -root)
 else
-  echo "custom chosen"
+  echo "Custom window chosen."
   XWININFO=$(xwininfo)
 fi
 
-# Get Window ID and dimensions, make sure dimensions are divisible by two, or else the encoder will fail
+# Get Window ID and dimensions, make sure X and Y dimensions are
+# divisible by two, or else the encoder will fail
 WID=$(echo "${XWININFO}" | grep "Window id:" | awk '{print $4}')
-WIDTH=$(echo "${XWININFO}" | grep "Width: " | cut -d ":" -f 2 | awk '{print $1+$1%2}')
-HEIGHT=$(echo "${XWININFO}" | grep "Height: " | cut -d ":" -f 2 | awk '{print $1+$1%2}')
+WIDTH=$(echo "${XWININFO}" | grep "Width: " | \
+ cut -d ":" -f 2 | awk '{print $1+$1%2}')
+HEIGHT=$(echo "${XWININFO}" | grep "Height: " | \
+ cut -d ":" -f 2 | awk '{print $1+$1%2}')
 
 # Calculate a suitable bitrate based on window dimensions
-BITRATE=$(echo "${WIDTH} * ${HEIGHT} * 0.006" | bc | cut -d "." -f 1 )
+BITRATE=$(echo "${WIDTH} * ${HEIGHT} * 0.0075" | bc | cut -d "." -f 1 )
 
 # Set file name.
 FILENAME="screencast-$(date +%F-%H-%M-%S).mp4"
 
 # Enable inputs as suitable
 if [ ${PLAYMON} != "none" ]; then
-  MONITORARG="pulsesrc device=${PLAYMON} slave-method=0 provide-clock=false ! audiorate ! audioconvert ! ffenc_aac bitrate=256000 ! queue2 ! mux."
+  MONITORARG="pulsesrc device=${PLAYMON} slave-method=0 provide-clock=false \
+   ! audiorate ! audioconvert ! ffenc_aac bitrate=256000 ! queue2 ! mux."
 fi
 if [ ${MIC} != "none" ]; then
-  MICARG="pulsesrc device=${MIC} slave-method=0 provide-clock=false ! audiorate ! audioconvert ! ffenc_aac bitrate=256000 ! queue2 ! mux."
+  MICARG="pulsesrc device=${MIC} slave-method=0 provide-clock=false \
+   ! audiorate ! audioconvert ! ffenc_aac bitrate=256000 ! queue2 ! mux."
 fi
 
-# Launch gstreamer
-gst-launch -e ximagesrc xid="${WID}" do-timestamp=1 use-damage=0 ! video/x-raw-rgb,framerate=30/1 \
- ! ffmpegcolorspace ! videoscale method=0 ! video/x-raw-yuv,width=${WIDTH},height=${HEIGHT} \
- ! x264enc speed-preset=ultrafast bitrate=${BITRATE} ! queue2 \
- ! mp4mux name="mux" ! filesink location="${HOME}/${FILENAME}" $MONITORARG $MICARG
-
+# Launch gstreamer (Using gstreamer 0.10)
+gst-launch-0.10 -q -e ximagesrc xid="${WID}" do-timestamp=1 use-damage=0 \
+ ! video/x-raw-rgb,framerate=30/1 ! ffmpegcolorspace  ! videoscale method=0 \
+ ! video/x-raw-yuv,width=${WIDTH},height=${HEIGHT} \
+ ! x264enc speed-preset=veryfast bitrate=${BITRATE} ! queue2 \
+ ! mp4mux name="mux" \
+ ! filesink location="${HOME}/${FILENAME}" ${MONITORARG} ${MICARG}
+
+# Launch gstreamer (Using gstreamer 1.0)
+#gst-launch-1.0 -q -e ximagesrc xid="${WID}" do-timestamp=1 use-damage=0 \
+# ! video/x-raw,framerate=30/1 ! videoscale method=0 \
+# ! video/x-raw,width=${WIDTH},height=${HEIGHT} ! videoconvert \
+# ! x264enc speed-preset=veryfast bitrate=${BITRATE} ! queue2 \
+# ! filesink location="${HOME}/${FILENAME}" ${MONITORARG} ${MICARG}
+
+echo "Recording done, file is ${FILENAME}"