Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
308 lines (209 sloc) 7.68 KB
package App::BCVI::NotifyClient;
use warnings;
use strict;
our $VERSION = '1.00';
use POSIX qw(setsid);
my $default_idle_time = 20; # seconds
my $default_poll_interval = 5; # seconds
sub send_command {
my($self, @args) = @_;
my $command = $self->opt('command') || $self->default_command();
if($command eq 'notify') {
# Check for TTY monitoring options; handle them and exit
# Otherwise drop through for default handling
return $self->SUPER::send_command(@args);
sub _notify_handle_tty_monitoring {
my($self, @args) = @_;
my %opt = $self->_notify_get_options(@args) or return;
$opt{tty} ||= $self->_notify_current_tty;
if($opt{idle}) {
print "Starting background process to monitor $opt{tty} for $opt{idle} second idle period\n";
print "Kill monitor with: bnotify --kill\n";
$opt{output} = $default_poll_interval;
$self->_notify_fork_bg_tty_monitor(sub {
while(1) {
$self->SUPER::send_command("terminal is idle");
$self->_notify_exit if $opt{once};
elsif($opt{output}) {
print "Starting background process to monitor $opt{tty} for output\n";
print "Kill monitor with: bnotify --kill\n";
$opt{idle} = $default_idle_time;
$self->_notify_fork_bg_tty_monitor(sub {
while(1) {
$self->SUPER::send_command("output received");
$self->_notify_exit if $opt{once};
elsif($opt{kill}) {
sub _notify_wait_for_idle {
my($self, $opt) = @_;
my $sleep_time = $opt->{idle};
while(1) {
my $mtime = $self->_notify_tty_mtime($opt);
my $idle = time() - $mtime;
$sleep_time = $opt->{idle} - $idle;
return if $sleep_time <= 0;
sub _notify_wait_for_output {
my($self, $opt) = @_;
my $mtime = $self->_notify_tty_mtime($opt);
while(1) {
return if $self->_notify_tty_mtime($opt) != $mtime;
sub _notify_tty_mtime {
my($self, $opt) = @_;
my @stat = stat($opt->{tty}) or die "stat($opt->{tty}): $!";
return $stat[9];
sub _notify_current_tty {
my $tty = readlink("/proc/self/fd/0");
if(!$tty) {
chomp( $tty = `tty` );
die "Unable to determine TTY\n" unless $tty;
return $tty;
sub _notify_get_options {
my($self, @args) = @_;
local(@ARGV) = @args;
my %opt = ();
) or exit;
if(defined($opt{idle})) {
$opt{idle} ||= $default_idle_time;
if(defined($opt{output})) {
$opt{output} ||= $default_poll_interval;
if(defined($opt{tty}) and ! -e $opt{tty}) {
die "No such file or device: $opt{tty}\n";
return %opt;
sub _notify_fork_bg_tty_monitor {
my($self, $monitor_sub) = @_;
fork() && exit; # Parent retuns to shell child continues in background
# We have this code lying around - might as well abuse it
exit; # Shouldn't be reached
sub _notify_exit {
my($self) = @_;
unlink $self->pid_file;
sub pid_file {
my($self) = @_;
my $path = $self->_notify_current_tty;
$path =~ s{^.*(?=tty|pty|pts)}{}i;
$path =~ s{^/}{};
$path =~ s{\W+}{-}g;
return File::Spec->catfile($self->conf_directory(), "notify-$");
'test -n "${BCVI_CONF}" && alias bnotify="bcvi --no-path-xlate -c notify --"',
=head1 NAME
App::BCVI::NotifyClient - Send a notification message back to the user's desktop
Send a message to the desktop notifications widget on your workstation:
$ long-running-command; bnotify "long-running-command has finished"
or fork a background monitor to advise you when a subsequent command pauses for
$ bnotify -i
Starting background process to monitor /dev/pts/0 for 5 second idle period
$ sudo apt-get dist-upgrade
This module is a plugin for C<bcvi> (see: L<App::BCVI>). It uses the C<notify>
command to send a message back for display on the user's desktop. This plugin
assumes a plugin back on the workstation will route the message to the desktop
notification applet or use some other mechanism to bring it to the attention of
the user. The L<App::BCVI::NotifyDesktop> plugin is one implementation of the
workstation-end of the protocol.
The plugin registers the C<bnotify> alias as:
alias bnotify="bcvi --no-path-xlate -c notify"
This alias might be used to signal the user that a long-running process has
completed on a remote server, for example:
pg_dump intranet >intranet.dump ; bnotify "Database dump is finished!"
=head1 OPTIONS
Instead of simply sending a message, you can provide options to fork a
background process which will send you a message later when something
interesting happens (or doesn't):
=over 4
=item B<< --idle [<seconds>] >> (alias -i)
If the specified number of seconds (default 20) elapse with no output being
written to the TTY, you will receive a notification that the TTY is idle. For
example you might use this option to fork a listener and then kick off a
dist-upgrade command. If the command pauses awaiting input then you will be
=item B<< --output [<seconds>] >> (alias -o)
The C<--output> option is essentially the opposite of the C<--idle> - it will
tell you when there I<has> been output on the TTY. By default it will check
every 5 seconds. You can specify a different poll interval but this may mean
your notifications take longer to arrive.
When you specify C<--output>, the listener process will actually wait for 20
seconds B<of idle time> before it starts looking for output. This allows you
to kick off the command you wish to monitor without getting alerts as you
=item B<< --tty <path> >> (alias -t)
The C<--idle> and C<--output> options will monitor the current TTY by default.
This option allows you to monitor a different TTY or even a plain file.
=item B<< --once >> (alias -1)
The C<--idle> and C<--output> option will loop and notify you of each idle time
or output event. Use the C<<--once>> option if you only want to be told about
the first event. The background process will exit immediately after sending
the notification.
=item B<< --kill >> (alias -k)
Find and kill the background listener associated with the current TTY.
=head1 SUPPORT
You can look for information at:
=over 4
=item * Source code
L<> (under 'plugins')
=item * RT: CPAN's request tracker
=item * AnnoCPAN: Annotated CPAN documentation
=item * CPAN Ratings
=item * Search CPAN
Copyright 2010 Grant McLean E<lt>grantm@cpan.orgE<gt>
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
Something went wrong with that request. Please try again.