Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into zipapp
Browse files Browse the repository at this point in the history
  • Loading branch information
dschep committed Mar 31, 2016
2 parents 06f9fb0 + f1679ea commit f97cc6d
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 23 deletions.
61 changes: 50 additions & 11 deletions ntfy/cli.py
Expand Up @@ -21,22 +21,33 @@
from . import __version__, notify
from .config import load_config, DEFAULT_CONFIG, OLD_DEFAULT_CONFIG
from .data import scripts
try:
from .terminal import is_focused
except ImportError:
is_focused = lambda: True


def run_cmd(args):
if getattr(args, 'pid', False):
return watch_pid(args)
if not args.command:
stderr.write('usage: ntfy done [-h|-L N] command\n'
'ntfy done: error: the following arguments '
'are required: command\n')
exit(1)

start_time = time()
retcode = call(args.command)
duration = time() - start_time
if args.formatter:
args.command, retcode, duration = args.formatter
args.command, retcode, duration = (
[args.command], int(retcode), int(duration))
else:
stderr.write('usage: ntfy done [-h|-L N] command\n'
'ntfy done: error: the following arguments '
'are required: command\n')
exit(1)
else:
start_time = time()
retcode = call(args.command)
duration = time() - start_time
if args.longer_than is not None and duration <= args.longer_than:
return
if args.unfocused_only and is_focused():
return
if emojize is not None and not args.no_emoji:
prefix = ':white_check_mark: ' if retcode == 0 else ':x: '
else:
Expand Down Expand Up @@ -68,8 +79,10 @@ def watch_pid(args):


def auto_done(args):
if emojize is not None and not args.no_emoji:
print('export AUTO_NTFY_DONE_EMOJI=true')
if args.longer_than:
print('export AUTO_NTFY_DONE_LONGER_THAN=-L{}'.format(args.longer_than))
if args.unfocused_only:
print('export AUTO_NTFY_DONE_UNFOCUSED_ONLY=-b')
if args.shell == 'bash':
print('source {}'.format(scripts['bash-preexec.sh']))
print('source {}'.format(scripts['auto-ntfy-done.sh']))
Expand Down Expand Up @@ -169,6 +182,19 @@ def __call__(self, parser, args, values, option_string=None):
type=int,
metavar='N',
help="Only notify if the command runs longer than N seconds")
done_parser.add_argument(
'-b',
'--background-only',
action='store_true',
default=False,
dest='unfocused_only',
help="Only notify if shell isn't in the foreground")
done_parser.add_argument(
'--formatter',
metavar=('command', 'retcode', 'duration'),
nargs=3,
help="Format and send cmd, retcode & duration instead of running command. "
"Used internally by shell-integration")
if psutil is not None:
done_parser.add_argument(
'-p',
Expand All @@ -185,7 +211,20 @@ def __call__(self, parser, args, values, option_string=None):
'--shell',
default=path.split(environ.get('SHELL', ''))[1],
choices=['bash', 'zsh'],
help='The shell to integrate ntfy with (default: your login shell)')
help='The shell to integrate ntfy with (default: $SHELL)')
shell_integration_parser.add_argument(
'-L',
'--longer-than',
type=int,
metavar='N',
help="Only notify if the command runs longer than N seconds")
shell_integration_parser.add_argument(
'-f',
'--foreground-too',
action='store_false',
default=True,
dest='unfocused_only',
help="Also notify if shell is in the foreground")
shell_integration_parser.set_defaults(func=auto_done)


Expand Down
19 changes: 7 additions & 12 deletions ntfy/shell_integration/auto-ntfy-done.sh
@@ -1,16 +1,16 @@
# In bash this requires https://github.com/rcaloras/bash-preexec
# If sourcing this via ntfy auto-done, it is sourced for you.

# Default timeout is 10 seconds.
AUTO_NTFY_DONE_TIMEOUT=${AUTO_NTFY_DONE_TIMEOUT:-10}
# Default to ignoring some well known interactive programs
AUTO_NTFY_DONE_IGNORE=${AUTO_NTFY_DONE_IGNORE:-ntfy emacs info less mail man meld most mutt nano screen ssh sudo tail tmux vi vim}
# Bash option example
#AUTO_NTFY_DONE_OPTS='-b default'
# Zsh option example
#AUTO_NTFY_DONE_OPTS=(-b default)
# force emoji example
#AUTO_NTFY_DONE_EMOJI=true
# notify for unfocused only (Used by ntfy internally)
#AUTO_NTFY_DONE_UNFOCUSED_ONLY=-b
# notify for commands runing longer than N sec only (Used by ntfy internally)
#AUTO_NTFY_DONE_LONGER_THAN=-L10

function _ntfy_precmd () {
[ -n "$ntfy_start_time" ] || return
Expand All @@ -21,14 +21,9 @@ function _ntfy_precmd () {
local appname=$(basename "${ntfy_command%% *}")
[[ " $AUTO_NTFY_DONE_IGNORE " == *" $appname "* ]] && return

local human_duration=$(printf '%d:%02d\n' $(($duration/60)) $(($duration%60)))
local human_retcode
[ "$ret_value" -eq 0 ] && human_retcode='succeeded' || human_retcode='failed'
local prefix
if [[ "$AUTO_NTFY_DONE_EMOJI" == "true" ]]; then
[ "$ret_value" -eq 0 ] && prefix=':white_check_mark: ' || prefix=':x: '
fi
ntfy $AUTO_NTFY_DONE_OPTS send "$prefix\"$ntfy_command\" $human_retcode in $human_duration minutes"
ntfy $AUTO_NTFY_DONE_OPTS done \
$AUTO_NTFY_DONE_UNFOCUSED_ONLY $AUTO_NTFY_DONE_LONGER_THAN \
--formatter "$ntfy_command" $ret_value $duration
}

function _ntfy_preexec () {
Expand Down
65 changes: 65 additions & 0 deletions ntfy/terminal.py
@@ -0,0 +1,65 @@
from os import environ, ttyname
from subprocess import check_output, Popen, PIPE
from sys import platform, stdout


def get_tty():

window_id = int(check_output(['xprop', '-root', '\t$0',
'_NET_ACTIVE_WINDOW']).split()[1], 16)
return int(environ['WINDOWID']) == window_id


def linux_window_is_focused():
window_id = int(check_output(['xprop', '-root', '\t$0',
'_NET_ACTIVE_WINDOW']).split()[1], 16)
return int(environ['WINDOWID']) == window_id


def osascript_tell(app, script):
p = Popen(['osascript'], stdin=PIPE, stdout=PIPE)
stdout, stderr = p.communicate(
'tell application "{}"\n{}\nend tell'.format(app, script))
return stdout.rstrip('\n')


def darwin_iterm2_shell_is_focused():
focused_tty = osascript_tell(
'iTerm',
'tty of current session of current terminal',
)
return focused_tty == ttyname(stdout.fileno())


def darwin_terminal_shell_is_focused():
focused_tty = osascript_tell(
'Terminal',
'tty of (first tab of (first window whose frontmost is true) '
'whose selected is true)',
)
return focused_tty == ttyname(stdout.fileno())


def darwin_app_shell_is_focused():
current_appid = {
'iTerm.app': 'iTerm',
'Apple_Terminal': 'Terminal',
}.get(environ.get('TERM_PROGRAM'))
focused_appid = osascript_tell(
'System Events',
'name of first application process whose frontmost is true',
)
if current_appid == focused_appid:
return {
'Terminal': darwin_terminal_shell_is_focused,
'iTerm': darwin_iterm2_shell_is_focused,
}[current_appid]()


def is_focused():
if platform.startswith('linux'):
return linux_window_is_focused()
elif platform == 'darwin':
return darwin_app_shell_is_focused()
else:
return True
5 changes: 5 additions & 0 deletions tests/ntfy_test/cli.py
Expand Up @@ -15,6 +15,7 @@ def test_default(self, mock_call):
args.longer_than = -1
args.command = ['true']
args.pid = None
args.unfocused_only = False
self.assertEqual('"true" succeeded in 0:00 minutes', run_cmd(args))

@patch('ntfy.cli.call')
Expand All @@ -25,12 +26,14 @@ def test_emoji(self, mock_call):
args.command = ['true']
args.pid = None
args.no_emoji = False
args.unfocused_only = False
self.assertEqual(':white_check_mark: "true" succeeded in 0:00 minutes',
run_cmd(args))

def tests_usage(self):
args = MagicMock()
args.pid = False
args.formatter = False
args.command = []
self.assertRaises(SystemExit, run_cmd, args)

Expand All @@ -41,6 +44,7 @@ def test_longerthan(self, mock_call):
args.longer_than = 1
args.command = ['true']
args.pid = None
args.unfocused_only = False
self.assertEqual(None, run_cmd(args))


Expand Down Expand Up @@ -69,6 +73,7 @@ def test_watch_pid(self, mock_process):
mock_process.return_value.cmdline.return_value = ['cmd']
args = MagicMock()
args.pid = 1
args.unfocused_only = False
self.assertEqual('PID[1]: "cmd" finished in 0:00 minutes',
run_cmd(args))

Expand Down

0 comments on commit f97cc6d

Please sign in to comment.