diff --git a/bin/long-running.bash b/bin/long-running.bash new file mode 100644 index 0000000..5b88110 --- /dev/null +++ b/bin/long-running.bash @@ -0,0 +1,108 @@ +# Copyright (c) 2008-2012 undistract-me developers. See LICENSE for details. +# +# Source this, and then run notify_when_long_running_commands_finish_install +# +# Relies on http://www.twistedmatrix.com/users/glyph/preexec.bash.txt + +# Generate a notification for any command that takes longer than this amount +# of seconds to return to the shell. e.g. if LONG_RUNNING_COMMAND_TIMEOUT=10, +# then 'sleep 11' will always generate a notification. + +# Default timeout is 10 seconds. +if [ -z "$LONG_RUNNING_COMMAND_TIMEOUT" ]; then + LONG_RUNNING_COMMAND_TIMEOUT=10 +fi + +# The pre-exec hook functionality is in a separate branch. +if [ -z "$LONG_RUNNING_PREEXEC_LOCATION" ]; then + LONG_RUNNING_PREEXEC_LOCATION=$HOME/bin/preexec.bash +fi + +if [ -f "$LONG_RUNNING_PREEXEC_LOCATION" ]; then + . $LONG_RUNNING_PREEXEC_LOCATION +else + echo "Could not find preexec.bash" +fi + + +function notify_when_long_running_commands_finish_install() { + + function active_window_id () { + if [[ -n $DISPLAY ]] ; then + set - $(xprop -root _NET_ACTIVE_WINDOW) + echo $5 + return + fi + echo nowindowid + } + + function sec_to_human () { + local H='' + local M='' + local S='' + + local h=$(($1 / 3600)) + [ $h -gt 0 ] && H="${h} hour" && [ $h -gt 1 ] && H="${H}s" + + local m=$((($1 / 60) % 60)) + [ $m -gt 0 ] && M=" ${m} min" && [ $m -gt 1 ] && M="${M}s" + + local s=$(($1 % 60)) + [ $s -gt 0 ] && S=" ${s} sec" && [ $s -gt 1 ] && S="${S}s" + + echo $H$M$S + } + + function precmd () { + + if [[ -n "$__udm_last_command_started" ]]; then + local now current_window + + printf -v now "%(%s)T" -1 + current_window=$(active_window_id) + if [[ $current_window != $__udm_last_window ]] || + [[ $current_window == "nowindowid" ]] ; then + local time_taken=$(( $now - $__udm_last_command_started )) + local time_taken_human=$(sec_to_human $time_taken) + local appname=$(basename "${__udm_last_command%% *}") + if [[ $time_taken -gt $LONG_RUNNING_COMMAND_TIMEOUT ]] && + [[ -n $DISPLAY ]] && + [[ ! " $LONG_RUNNING_IGNORE_LIST " == *" $appname "* ]] ; then + local icon=dialog-information + local urgency=low + if [[ $__preexec_exit_status != 0 ]]; then + icon=dialog-error + urgency=normal + fi + notify=$(command -v notify-send) + if [ -x "$notify" ]; then + $notify \ + -i $icon \ + -u $urgency \ + "Long command completed" \ + "\"$__udm_last_command\" took $time_taken_human" + else + echo -ne "\a" + fi + fi + if [[ -n $LONG_RUNNING_COMMAND_CUSTOM_TIMEOUT ]] && + [[ -n $LONG_RUNNING_COMMAND_CUSTOM ]] && + [[ $time_taken -gt $LONG_RUNNING_COMMAND_CUSTOM_TIMEOUT ]] ; then + # put in brackets to make it quiet + export __preexec_exit_status + ( $LONG_RUNNING_COMMAND_CUSTOM \ + "\"$__udm_last_command\" took $time_taken_human" & ) + fi + fi + fi + } + + function preexec () { + # use __udm to avoid global name conflicts + __udm_last_command_started=$(printf "%(%s)T\n" -1) + __udm_last_command=$(echo "$1") + __udm_last_window=$(active_window_id) + } + + preexec_install +} diff --git a/bin/preexec.bash b/bin/preexec.bash new file mode 100644 index 0000000..df21d44 --- /dev/null +++ b/bin/preexec.bash @@ -0,0 +1,180 @@ +# Copyright (c) 2008-2012 undistract-me developers. See LICENSE for details. +# +# preexec.bash -- Bash support for ZSH-like 'preexec' and 'precmd' functions. + +# The 'preexec' function is executed before each interactive command is +# executed, with the interactive command as its argument. The 'precmd' +# function is executed before each prompt is displayed. + +# To use, in order: + +# 1. source this file +# 2. define 'preexec' and/or 'precmd' functions (AFTER sourcing this file), +# 3. as near as possible to the end of your shell setup, run 'preexec_install' +# to kick everything off. + +# Note: this module requires 2 bash features which you must not otherwise be +# using: the "DEBUG" trap, and the "PROMPT_COMMAND" variable. preexec_install +# will override these and if you override one or the other this _will_ break. + +# This is known to support bash3, as well as *mostly* support bash2.05b. It +# has been tested with the default shells on MacOS X 10.4 "Tiger", Ubuntu 5.10 +# "Breezy Badger", Ubuntu 6.06 "Dapper Drake", and Ubuntu 6.10 "Edgy Eft". + + +# Copy screen-run variables from the remote host, if they're available. + +if [[ "$SCREEN_RUN_HOST" == "" ]] +then + SCREEN_RUN_HOST="$LC_SCREEN_RUN_HOST" + SCREEN_RUN_USER="$LC_SCREEN_RUN_USER" +fi + +# Default do-nothing implementation of preexec. +function preexec () { + true +} + +# Default do-nothing implementation of precmd. +function precmd () { + true +} + +# This function is installed as the PROMPT_COMMAND; it is invoked before each +# interactive prompt display. It sets a variable to indicate that the prompt +# was just displayed, to allow the DEBUG trap, below, to know that the next +# command is likely interactive. +function preexec_invoke_cmd () { + precmd + trap 'preexec_invoke_exec' DEBUG +} + +# This function is installed as the DEBUG trap. It is invoked before each +# interactive prompt display. Its purpose is to inspect the current +# environment to attempt to detect if the current command is being invoked +# interactively, and invoke 'preexec' if so. +function preexec_invoke_exec () { + if [[ -n "$COMP_LINE" ]] + then + # We're in the middle of a completer. This obviously can't be + # an interactively issued command. + return + fi + trap '' DEBUG + + if [[ "preexec_invoke_cmd" == "$BASH_COMMAND" ]] + then + # Sadly, there's no cleaner way to detect two prompts being displayed + # one after another. This makes it important that PROMPT_COMMAND + # remain set _exactly_ as below in preexec_install. Let's switch back + # out of interactive mode and not trace any of the commands run in + # precmd. + + # Given their buggy interaction between BASH_COMMAND and debug traps, + # versions of bash prior to 3.1 can't detect this at all. + return + fi + + # In more recent versions of bash, this could be set via the "BASH_COMMAND" + # variable, but using history here is better in some ways: for example, "ps + # auxf | less" will show up with both sides of the pipe if we use history, + # but only as "ps auxf" if not. + local this_command=`HISTTIMEFORMAT= history 1 | sed -e "s/^[ ]*[0-9]*[ ]*//g"`; + + # If none of the previous checks have earlied out of this function, then + # the command is in fact interactive and we should invoke the user's + # preexec hook with the running command as an argument. + preexec "$this_command" +} + +function preexec_set_exit () { + __preexec_exit_status=$? +} + +# Execute this to set up preexec and precmd execution. +function preexec_install () { + + # *BOTH* of these options need to be set for the DEBUG trap to be invoked + # in ( ) subshells. This smells like a bug in bash to me. The null stderr + # redirections are to quiet errors on bash2.05 (i.e. OSX's default shell) + # where the options can't be set, and it's impossible to inherit the trap + # into subshells. + + set -o functrace > /dev/null 2>&1 + shopt -s extdebug > /dev/null 2>&1 + + # Finally, install the actual traps. + if [ -n "$PROMPT_COMMAND" ]; then + PROMPT_COMMAND="preexec_set_exit;${PROMPT_COMMAND};preexec_invoke_cmd"; + else + PROMPT_COMMAND="preexec_set_exit;preexec_invoke_cmd"; + fi +} + +# Since this is the reason that 99% of everybody is going to bother with a +# pre-exec hook anyway, we'll include it in this module. + +# Change the title of the xterm. +function preexec_xterm_title () { + local title="$1" + echo -ne "\033]0;$title\007" > /dev/stderr +} + +function preexec_screen_title () { + local title="$1" + echo -ne "\033k$1\033\\" > /dev/stderr +} + +# Abbreviate the "user@host" string as much as possible to preserve space in +# screen titles. Elide the host if the host is the same, elide the user if the +# user is the same. +function preexec_screen_user_at_host () { + local RESULT="" + if [[ "$SCREEN_RUN_HOST" == "$SCREEN_HOST" ]] + then + return + else + if [[ "$SCREEN_RUN_USER" == "$USER" ]] + then + echo -n "@${SCREEN_HOST}" + else + echo -n "${USER}@${SCREEN_HOST}" + fi + fi +} + +function preexec_xterm_title_install () { + # These functions are defined here because they only make sense with the + # preexec_install below. + function precmd () { + preexec_xterm_title "${TERM} - ${USER}@${SCREEN_HOST} `dirs -0` $PROMPTCHAR" + if [[ "${TERM}" == screen ]] + then + preexec_screen_title "`preexec_screen_user_at_host`${PROMPTCHAR}" + fi + } + + function preexec () { + preexec_xterm_title "${TERM} - $1 {`dirs -0`} (${USER}@${SCREEN_HOST})" + if [[ "${TERM}" == screen ]] + then + local cutit="$1" + local cmdtitle=`echo "$cutit" | cut -d " " -f 1` + if [[ "$cmdtitle" == "exec" ]] + then + local cmdtitle=`echo "$cutit" | cut -d " " -f 2` + fi + if [[ "$cmdtitle" == "screen" ]] + then + # Since stacked screens are quite common, it would be nice to + # just display them as '$$'. + local cmdtitle="${PROMPTCHAR}" + else + local cmdtitle=":$cmdtitle" + fi + preexec_screen_title "`preexec_screen_user_at_host`${PROMPTCHAR}$cmdtitle" + fi + } + + preexec_install +} diff --git a/bin/undistract-me.sh b/bin/undistract-me.sh new file mode 100755 index 0000000..385bbf0 --- /dev/null +++ b/bin/undistract-me.sh @@ -0,0 +1,7 @@ +# Copyright (c) 2008-2012 undistract-me developers. See LICENSE for details. +# +# Check for interactive bash and that we haven't already been sourced. +[ -z "$BASH_VERSION" -o -z "$PS1" -o -n "$last_command_started_cache" ] && return + +. $HOME/bin/long-running.bash +notify_when_long_running_commands_finish_install