Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extract "foreground" detection and other improvements #62

Merged
merged 1 commit into from Nov 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions .zunit.yml
@@ -0,0 +1,8 @@
tap: false
directories:
tests: tests
output: tests/_output
support: tests/_support
time_limit: 0
fail_fast: false
allow_risky: false
31 changes: 17 additions & 14 deletions README.md
@@ -1,33 +1,35 @@
zsh-notify
=======
===

Desktop notifications for long running commands in ZSH.
Desktop notifications for long-running commands in ZSH.

Supported terminals and requirements
---

- On Mac OS X: Terminal.app or [iTerm2][iterm2];
- On macOS: Terminal.app or [iTerm2][iterm2];
- On Linux (and possibly other systems): any terminal application should be
supported as `xdotool` and `wmctrl` are used to query and modify windows
state.

When using the default notifier notifications are posted using
[terminal-notifier.app][terminal-notifier] on Mac OS X and `notify-send` on
other systems.
When using the default configuration, notifications are posted using
[terminal-notifier.app][terminal-notifier] on macOS and `notify-send` on
other systems; usage of custom notifier is described in [Configuration](#configuration).

When using Tmux on Yosemite, `reattach-to-user-namespace` is required to
prevent terminal-notifier to hang (see
[julienXX/terminal-notifier#115][issue115] for details).

Usage
Installation and usage
---

Just source notify.plugin.zsh.
Just clone this repository and source `notify.plugin.zsh` in your `~/.zshrc`,
or see below for instructions on installing with some popular package managers.

Configuration
---

Use `zstyle` in your `~/.zshrc`.
The behavior of zsh-notify can be modified by using `zstyle` **after**
sourcing `notify.plugin.zsh`.

- Set a custom title for error and success notifications, when using the
built-in notifier.
Expand All @@ -40,16 +42,17 @@ Use `zstyle` in your `~/.zshrc`.
zstyle ':notify:*' error-title "Command failed (in #{time_elapsed} seconds)"
zstyle ':notify:*' success-title "Command finished (in #{time_elapsed} seconds)"

- Change the notifications icons for failure or success. Any image path or URL
(Mac OS only) should work.
- Change the notifications icons for failure or success. Provide the path to an
image, or an URL if you are on macOS.

zstyle ':notify:*' error-icon "/path/to/error-icon.png"
zstyle ':notify:*' success-icon "/path/to/success-icon.png"

[Try this][dogefy.sh]. Wow.

- Set a sound for error and success notifications, when using the built-in
notifier. On Linux you should specify the path to an audio file.
- Play sounds with error and success notifications when using the built-in
notifier. Provide the path to an audio file, or the name of an "alert" sound
if you are on macOS.

zstyle ':notify:*' error-sound "Glass"
zstyle ':notify:*' success-sound "default"
Expand Down Expand Up @@ -84,7 +87,7 @@ Use `zstyle` in your `~/.zshrc`.
[dogefy.sh]: https://gist.github.com/marzocchi/14c47a49643389029a2026b4d4fec7ae
[issue115]: https://github.com/julienXX/terminal-notifier/issues/115

## Installation
## Installation with package managers

### [Antigen](https://github.com/zsh-users/antigen)

Expand Down
50 changes: 50 additions & 0 deletions applescript/is-terminal-active
@@ -0,0 +1,50 @@
# vim: set nowrap filetype=zsh:
#
# is-terminal-active exits with status 0 when the current shell is running on an
# active terminal window or tab, status 1 when the window or tab is in background
# and status 2 if the current terminal is not supported (eg. it's not iTerm2 nor
# Apple Terminal).
() {
local plugin_dir

zstyle -s ':notify:' plugin-dir plugin_dir
source $plugin_dir/lib

# run an AppleScript, selected from ./resources by basename given as the
# first argument, with all other arguments are positional arguments to the
# script's `on run` handler.
function run-applescript() {
local script_name impl

zstyle -s ':notify:' impl impl

script_name="$1"
shift

"$plugin_dir"/"$impl"/resources/"$script_name".applescript $@ 2>/dev/null
}

# exit with code 0 if the terminal window/tab is active, code 1 if inactive.
function is-terminal-window-active {
local term

if [[ "$TERM_PROGRAM" == 'iTerm.app' ]]; then
term=iterm2
elif [[ "$TERM_PROGRAM" == 'Apple_Terminal' ]]; then
term=apple-terminal
else
return 2
fi

run-applescript is-"$term"-active "$(current-tty)"
}

if is-terminal-window-active; then
if is-inside-tmux; then
is-current-tmux-pane-active
return $?
fi
else
return $?
fi
}
24 changes: 24 additions & 0 deletions applescript/resources/is-apple-terminal-active.applescript
@@ -0,0 +1,24 @@
#!/usr/bin/osascript -ss

on run ttyName
try
set ttyName to first item of ttyName
on error
set ttyName to ""
end try

if ttyName is equal to "" then error "Usage: is-apple-terminal-active.applescript TTY"

tell application id "com.apple.terminal"
if frontmost is not true then error "Apple Terminal is not the frontmost application"

-- fun stuff, with 2 tabs in one window AS reports 2 windows with one
-- tab each, and all the tabs are frontmost!
repeat with t in tabs of (windows whose frontmost is true)
if t's tty is equal to ttyName then return
end repeat

error "Cannot find an active tab for '" & ttyName & "'"

end tell
end run
23 changes: 23 additions & 0 deletions applescript/resources/is-iterm2-active.applescript
@@ -0,0 +1,23 @@
#!/usr/bin/osascript

on run ttyName
try
set ttyName to first item of ttyName
on error
set ttyName to ""
end

if ttyName is equal to "" then error "Usage: is-iterm2-active.applescript TTY"

tell application "System Events"
tell item 1 of (application processes whose bundle identifier is "com.googlecode.iterm2")
if frontmost is not true then error "iTerm is not the frontmost application"
end tell
end tell

tell application id "com.googlecode.iterm2"
set currentTty to tty of (current session of current tab of current window) as text
if currentTty is not equal to ttyName then error "Current tab TTY '" & currentTty & "' does not match expected '" & ttyName & "'"
end tell

end run
49 changes: 49 additions & 0 deletions applescript/zsh-notify
@@ -0,0 +1,49 @@
# vim: set nowrap filetype=zsh:
local message title time_elapsed type app_id app_id_option sound_option

if ! command -v terminal-notifier > /dev/null 2>&1; then
echo cannot find terminal-notifier in \$PATH 1>&2
return 1
fi

if [[ $# -lt 2 ]]; then
echo usage: zsh-notify TYPE TIME_ELAPSED 1>&2
return 1
fi

zstyle -s ':notify:' plugin-dir plugin_dir
source "$plugin_dir"/lib

type="$1"
time_elapsed="$(format-time $2)"
message=$(<&0)

zstyle -s ':notify:' "$type"-sound notification_sound
zstyle -s ':notify:' "$type"-icon icon

title=$(notification-title "$type" time_elapsed "$time_elapsed")

if [[ "$TERM_PROGRAM" == 'iTerm.app' ]]; then
app_id="com.googlecode.iterm2"
elif [[ "$TERM_PROGRAM" == 'Apple_Terminal' ]]; then
app_id="com.apple.terminal"
fi

if [[ -n "$app_id" ]]; then
app_id_option="-activate $app_id"
fi

if [[ -n "$notification_sound" ]]; then
sound_option="-sound $notification_sound"
fi

if [[ -n "$icon" ]]; then
icon_option="-appIcon $icon"
fi

terminal-notifier ${=app_id_option} ${=sound_option} ${=icon_option} \
-title "${title}" <<< "$message" > /dev/null 2>&1 &!

if zstyle -t ':notify:' activate-terminal; then
osascript <<< "tell app id \"$app_id\" to activate" 1>/dev/null
fi
69 changes: 69 additions & 0 deletions lib
@@ -0,0 +1,69 @@
# vim: set nowrap filetype=zsh:
#
# Functions shared by the mac and linux implementations.

# Exit with 0 if inside a TMUX pane
function is-inside-tmux {
[[ "$TMUX" != "" ]]
}

# Find the TTY for the current shell, also accounting for TMUX.
function current-tty {
if is-inside-tmux; then
tmux display-message -p '#{client_tty}'
else
echo $TTY
fi
}

# Exit with 0 if given TMUX pane is the active one.
function is-current-tmux-pane-active {
is-inside-tmux || return 1

local active_pane_id=$(tmux list-windows -F '#{window_active} #{pane_id}' | grep -i '^1' | awk '{ print $2 }')

if [[ "$TMUX_PANE" == "$active_pane_id" ]]; then
return 0
fi

return 1
}

# Retrieve the title template from zstyle by type from first argument (one of 'success' or 'error')
# and replace every #{placeholder} from key/value pairs passed in the rest of arguments
function notification-title {
local type title k v

type="$1"
shift

zstyle -s ':notify:' "$type"-title title

while [[ $# -gt 0 ]]; do
k="$1"
v="$2"
title=$(echo $title | sed "s/#{$k}/$v/")
shift
shift
done

echo $title
}

# format-time takes a number of seconds as first argument and format it
# as Xs, XX:XX or XX:XX:XX
function format-time() {
local format t
t="$1"

if [[ "$t" -lt 60 ]]; then
format="%ss"
elif [[ "$t" -lt 3600 ]]; then
format="%M:%S"
else
format="%H:%M:%S"
fi

zmodload -e zsh/datetime || zmodload zsh/datetime
TZ=UTC strftime "$format" "$t"
}