From e7fb5658bedaca9df2fe8ae5ea64edb5dc6b15ab Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Sat, 26 Mar 2016 17:13:11 -0400 Subject: [PATCH 1/6] remove shell formatting logic (DRY!) Didn't de-dup --longer-than functionality because it avoids ntfy invoication removed the emoji passthrough. this is in part because it will always be backed in once the single file binary install is done :) --- ntfy/cli.py | 33 +++++++++++++++--------- ntfy/shell_integration/auto-ntfy-done.sh | 12 ++------- tests/ntfy_test/cli.py | 1 + 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/ntfy/cli.py b/ntfy/cli.py index 2a3b740..c83fa20 100644 --- a/ntfy/cli.py +++ b/ntfy/cli.py @@ -26,16 +26,21 @@ 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.longer_than is not None and duration <= args.longer_than: - return + 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 emojize is not None and not args.no_emoji: prefix = ':white_check_mark: ' if retcode == 0 else ':x: ' else: @@ -68,8 +73,6 @@ def watch_pid(args): def auto_done(args): shell_path = path.join(path.split(__file__)[0], 'shell_integration') - if emojize is not None and not args.no_emoji: - print('export AUTO_NTFY_DONE_EMOJI=true') if args.shell == 'bash': print('source {}/bash-preexec.sh'.format(shell_path)) print('source {}/auto-ntfy-done.sh'.format(shell_path)) @@ -169,6 +172,12 @@ 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( + '--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', diff --git a/ntfy/shell_integration/auto-ntfy-done.sh b/ntfy/shell_integration/auto-ntfy-done.sh index b8726c7..1cf30a9 100644 --- a/ntfy/shell_integration/auto-ntfy-done.sh +++ b/ntfy/shell_integration/auto-ntfy-done.sh @@ -9,8 +9,6 @@ AUTO_NTFY_DONE_IGNORE=${AUTO_NTFY_DONE_IGNORE:-ntfy emacs info less mail man mel #AUTO_NTFY_DONE_OPTS='-b default' # Zsh option example #AUTO_NTFY_DONE_OPTS=(-b default) -# force emoji example -#AUTO_NTFY_DONE_EMOJI=true function _ntfy_precmd () { [ -n "$ntfy_start_time" ] || return @@ -21,14 +19,8 @@ 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 --formatter \ + "$ntfy_command" $ret_value $duration } function _ntfy_preexec () { diff --git a/tests/ntfy_test/cli.py b/tests/ntfy_test/cli.py index 82ab615..ed593de 100644 --- a/tests/ntfy_test/cli.py +++ b/tests/ntfy_test/cli.py @@ -31,6 +31,7 @@ def test_emoji(self, mock_call): def tests_usage(self): args = MagicMock() args.pid = False + args.formatter = False args.command = [] self.assertRaises(SystemExit, run_cmd, args) From e4f9322c3636862b9b30d5a4c3f3f4e406a21c53 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 28 Mar 2016 22:01:46 -0400 Subject: [PATCH 2/6] only send notifications when not focused! --- ntfy/cli.py | 19 +++++++ ntfy/shell_integration/auto-ntfy-done.sh | 6 ++- ntfy/terminal.py | 65 ++++++++++++++++++++++++ tests/ntfy_test/cli.py | 4 ++ 4 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 ntfy/terminal.py diff --git a/ntfy/cli.py b/ntfy/cli.py index c83fa20..f5ab934 100644 --- a/ntfy/cli.py +++ b/ntfy/cli.py @@ -20,6 +20,7 @@ from . import __version__, notify from .config import load_config, DEFAULT_CONFIG, OLD_DEFAULT_CONFIG +from .terminal import is_focused def run_cmd(args): @@ -41,6 +42,8 @@ def run_cmd(args): 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: @@ -73,6 +76,8 @@ def watch_pid(args): def auto_done(args): shell_path = path.join(path.split(__file__)[0], 'shell_integration') + if args.unfocused_only: + print('export AUTO_NTFY_DONE_UNFOCUSED_ONLY=-b') if args.shell == 'bash': print('source {}/bash-preexec.sh'.format(shell_path)) print('source {}/auto-ntfy-done.sh'.format(shell_path)) @@ -172,6 +177,13 @@ 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'), @@ -195,6 +207,13 @@ def __call__(self, parser, args, values, option_string=None): default=path.split(environ.get('SHELL', ''))[1], choices=['bash', 'zsh'], help='The shell to integrate ntfy with (default: your login shell)') +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) diff --git a/ntfy/shell_integration/auto-ntfy-done.sh b/ntfy/shell_integration/auto-ntfy-done.sh index 1cf30a9..70000e2 100644 --- a/ntfy/shell_integration/auto-ntfy-done.sh +++ b/ntfy/shell_integration/auto-ntfy-done.sh @@ -9,6 +9,8 @@ AUTO_NTFY_DONE_IGNORE=${AUTO_NTFY_DONE_IGNORE:-ntfy emacs info less mail man mel #AUTO_NTFY_DONE_OPTS='-b default' # Zsh option example #AUTO_NTFY_DONE_OPTS=(-b default) +# notify for unfocused only +#AUTO_NTFY_DONE_UNFOCUSED_ONLY=-b function _ntfy_precmd () { [ -n "$ntfy_start_time" ] || return @@ -19,8 +21,8 @@ function _ntfy_precmd () { local appname=$(basename "${ntfy_command%% *}") [[ " $AUTO_NTFY_DONE_IGNORE " == *" $appname "* ]] && return - ntfy $AUTO_NTFY_DONE_OPTS done --formatter \ - "$ntfy_command" $ret_value $duration + ntfy $AUTO_NTFY_DONE_OPTS done $AUTO_NTFY_DONE_UNFOCUSED_ONLY \ + --formatter "$ntfy_command" $ret_value $duration } function _ntfy_preexec () { diff --git a/ntfy/terminal.py b/ntfy/terminal.py new file mode 100644 index 0000000..0acc247 --- /dev/null +++ b/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 diff --git a/tests/ntfy_test/cli.py b/tests/ntfy_test/cli.py index ed593de..1ef9328 100644 --- a/tests/ntfy_test/cli.py +++ b/tests/ntfy_test/cli.py @@ -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') @@ -25,6 +26,7 @@ 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)) @@ -42,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)) @@ -70,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)) From 90b410b7c073581102f1339d94dbd5486c695ed8 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 29 Mar 2016 01:24:51 -0400 Subject: [PATCH 3/6] make timeout defualt for shell integeration 0 since it doesn't notify when the shell is in the foreground --- ntfy/shell_integration/auto-ntfy-done.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ntfy/shell_integration/auto-ntfy-done.sh b/ntfy/shell_integration/auto-ntfy-done.sh index 70000e2..4136044 100644 --- a/ntfy/shell_integration/auto-ntfy-done.sh +++ b/ntfy/shell_integration/auto-ntfy-done.sh @@ -2,7 +2,7 @@ # 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} +AUTO_NTFY_DONE_TIMEOUT=${AUTO_NTFY_DONE_TIMEOUT:-0} # 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 From 9cc4c0491b4c8ddc332c32c800cfd5a4e62dae73 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 29 Mar 2016 10:28:16 -0400 Subject: [PATCH 4/6] longerthan passthrough --- ntfy/cli.py | 8 ++++++++ ntfy/shell_integration/auto-ntfy-done.sh | 9 +++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/ntfy/cli.py b/ntfy/cli.py index f5ab934..dee0280 100644 --- a/ntfy/cli.py +++ b/ntfy/cli.py @@ -76,6 +76,8 @@ def watch_pid(args): def auto_done(args): shell_path = path.join(path.split(__file__)[0], 'shell_integration') + 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': @@ -207,6 +209,12 @@ def __call__(self, parser, args, values, option_string=None): default=path.split(environ.get('SHELL', ''))[1], choices=['bash', 'zsh'], help='The shell to integrate ntfy with (default: your login 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', diff --git a/ntfy/shell_integration/auto-ntfy-done.sh b/ntfy/shell_integration/auto-ntfy-done.sh index 4136044..17e81a6 100644 --- a/ntfy/shell_integration/auto-ntfy-done.sh +++ b/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:-0} # 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) -# notify for unfocused only +# 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 @@ -21,7 +21,8 @@ function _ntfy_precmd () { local appname=$(basename "${ntfy_command%% *}") [[ " $AUTO_NTFY_DONE_IGNORE " == *" $appname "* ]] && return - ntfy $AUTO_NTFY_DONE_OPTS done $AUTO_NTFY_DONE_UNFOCUSED_ONLY \ + ntfy $AUTO_NTFY_DONE_OPTS done \ + $AUTO_NTFY_DONE_UNFOCUSED_ONLY $AUTO_NTFY_DONE_LONGER_THAN \ --formatter "$ntfy_command" $ret_value $duration } From 2ad8bb145c1c4dbce97eb228e44b336a73c044d4 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 29 Mar 2016 10:28:48 -0400 Subject: [PATCH 5/6] just say $SHELL instead of your :shell: --- ntfy/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ntfy/cli.py b/ntfy/cli.py index dee0280..ae34c81 100644 --- a/ntfy/cli.py +++ b/ntfy/cli.py @@ -208,7 +208,7 @@ 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', From e9e92d97e89e1337f08c68d1121e64b68d823403 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 29 Mar 2016 19:52:08 -0400 Subject: [PATCH 6/6] :white_check_mark::green_heart: windows doesn't have ttyname --- ntfy/cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ntfy/cli.py b/ntfy/cli.py index ae34c81..553c7b3 100644 --- a/ntfy/cli.py +++ b/ntfy/cli.py @@ -20,7 +20,10 @@ from . import __version__, notify from .config import load_config, DEFAULT_CONFIG, OLD_DEFAULT_CONFIG -from .terminal import is_focused +try: + from .terminal import is_focused +except ImportError: + is_focused = lambda: True def run_cmd(args):