http://qs321.pair.com?node_id=482992
Category: Win32 Stuff
Author/Contact Info
Description: When I leave my Putty sessions open overnight or even over lunch, they die from lack of activity. For whatever reason, putty's built in keep-alive isn't good enough. This program hangs around until you've been inactive for awhile and every minute frobs any open putty windows with control-g. That's just the key I liked, use your own if you like.
#!perl
use strict;
use warnings;
use Carp 'croak';

# Win32::Process::Info and Win32::GuiTest can be installed from ppm.

use Win32::Process::Info;

use Win32::GuiTest qw( GetForegroundWindow SetForegroundWindow
FindWindowLike SendKeys GetCursorPos GetWindowText );

# Enables debugging output whenever the program is run from a terminal
+.
# For normal use, run this with wperl.exe
use constant DEBUG => -t STDOUT;

# I like emacs, yes I do! I like emacs, how about you?
use constant KEY_SEQUENCE => "^g";
use constant PUTTY => qr/ - PuTTY$/;
use constant REPEATING_SIGNATURE => qr/^((?>\d+)(?>(?:,(?>\d+))+))(?>(
+?: \1){59,59})$/;

# Generated from YAPE::Regex::Explain:
# The regular expression:
# 
# ^((?>\d+)(?>(?:,(?>\d+))+))(?>(?: \1){59,59})$
# 
# matches as follows:
# 
# NODE                     EXPLANATION
# --------------------------------------------------------------------
# ^                        the beginning of the string
# --------------------------------------------------------------------
# (                        group and capture to \1:
# --------------------------------------------------------------------
#   (?>                      match (and do not backtrack afterwards):
# --------------------------------------------------------------------
#     \d+                      digits (0-9) (1 or more times
#                              (matching the most amount possible))
# --------------------------------------------------------------------
#   )                        end of look-ahead
# --------------------------------------------------------------------
#   (?>                      match (and do not backtrack afterwards):
# --------------------------------------------------------------------
#     (?:                      group, but do not capture (1 or more
#                              times (matching the most amount
#                              possible)):
# --------------------------------------------------------------------
#       ,                        ','
# --------------------------------------------------------------------
#       (?>                      match (and do not backtrack
#                                afterwards):
# --------------------------------------------------------------------
#         \d+                      digits (0-9) (1 or more times
#                                  (matching the most amount
#                                  possible))
# --------------------------------------------------------------------
#       )                        end of look-ahead
# --------------------------------------------------------------------
#     )+                       end of grouping
# --------------------------------------------------------------------
#   )                        end of look-ahead
# --------------------------------------------------------------------
# )                        end of \1
# --------------------------------------------------------------------
# (?>                      match (and do not backtrack afterwards):
# --------------------------------------------------------------------
#   (?:                      group, but do not capture (between 59
#                            and 59 times (matching the most amount
#                            possible)):
# --------------------------------------------------------------------
#                              ' '
# --------------------------------------------------------------------
#     \1                       what was matched by capture \1
# --------------------------------------------------------------------
#   ){59,59}                 end of grouping
# --------------------------------------------------------------------
# )                        end of look-ahead
# --------------------------------------------------------------------
# $                        before an optional \n, and the end of the
#                          string
# --------------------------------------------------------------------

# If we are started and this program is already running, die.
LockOrDie();

my $lastfrob = time;
my @snapshots;
while ( 1 ) {
    # Don't even bother watching for inactivity if there is no PuTTY a
+round to frongle.
    WaitWindowLike( undef, PUTTY );
    
    # A queue, one minute long, of snapshots of system state.
    push @snapshots, join( ",",
                           # Something non-numeric snuck in, I think.
                           grep /^\d+$/,
                           GetForegroundWindow(), GetCursorPos(), Find
+WindowLike() );
    shift @snapshots if @snapshots > 60;
    
    my $elapsed = time - $lastfrob;
    my $frotz = "@snapshots" =~ REPEATING_SIGNATURE;
    print "$elapsed $frotz\n"
      if DEBUG;
    if ( $elapsed >= 30 and $frotz ) {
        frobnicate_putty();
        $lastfrob = time;
    }
    
    sleep 1;
}

sub WaitWindowLike {
    while ( 1 ) {
        return if FindWindowLike( @_ );
        sleep 60;
    }
}

sub frobnicate_putty {
    my $active = GetForegroundWindow();
    my @windows = FindWindowLike( undef, PUTTY );
    print "Frobbing \@ @{[scalar time]}\n" if DEBUG and @windows;
    for my $window ( @windows ) {
        print "   $window: " . GetWindowText( $window ) . "\n"
          if DEBUG;
        
        # This is utterly obnoxious. Yuck.
        SetForegroundWindow( $window );
        
        SendKeys( KEY_SEQUENCE );
    }
    
    SetForegroundWindow( $active );
}

sub LockOrDie {
    my $lock_file = File::Spec->tmpdir() . "/$0.lck";
    my ( $lock_pid )
      = grep /^\d+$/,
        eval { do { local @ARGV = $lock_file;
                    <> } };
    
    my ( $proc )
      = grep { $_->{ProcessId} == $lock_pid
                 and $_->{Name} =~ /perl/i }
        Win32::Process::Info->new->GetProcInfo;
    croak "$0 appears to be already running as $proc->{ProcessId}."
      if $proc;
    
    open my $fh, ">", $lock_file
      or croak "Can't open $lock_file for writing: $^E";
    print $fh "$$\n"
      or croak "Can't write to $lock_file for writing: $^E";
    close $fh
      or croak "Can't close and flush $lock_file after writing: $^E";
    
    return 1;
}
Replies are listed 'Best First'.
Re: A "better" Putty keep-alive
by merlyn (Sage) on Aug 11, 2005 at 15:33 UTC
      (Someone has to say it...)

      Well, yes, but then you're stuck with emacs on your screen. Much worse than having to log back in :)

      (Just kidding, folks! As long as emacs works for him, that's as good a solution as any...)


      Mike
Re: A "better" Putty keep-alive
by davidrw (Prior) on Aug 11, 2005 at 16:16 UTC
    server-side method instead of your client-side (so a tradeoff there), but this is what i usually use:
    i put this in my .bashrc (if statement is so that it won't run during batch ssh or scp logins):
    if test "xterm" = "$TERM" ; then /home/myname/bin/keepalive & fi
    and /home/myname/bin/keepalive is just (periodically print a space and a backspace):
    #!/bin/bash while test 1 ; do sleep 600; echo -ne " \b"; done
    Only side effect is that if a vi is left open, it appears to make the character under the cursor disappear (but really doesn't) .. ctrl-L to refresh the screen "fixes" it if i even notice it.
Re: A "better" Putty keep-alive
by tinita (Parson) on Aug 12, 2005 at 09:54 UTC
    i like to use screen on the serverside, whenever possible.
    log in the next day and type screen -r, and everything is back (unless some sysadmin decided to empty the /tmp or to reboot...)
    screen++
Re: A "better" Putty keep-alive
by shiza (Hermit) on Aug 11, 2005 at 17:55 UTC
    I haven't had to think about that problem since I switched to a Linux OS on my workstation :^P
Re: A "better" Putty keep-alive
by converter (Priest) on Aug 12, 2005 at 02:55 UTC
    Try setting putty's "Seconds between keepalives" option to something greater than zero, 300 is probably a good number. Since I set this option I've had no problem keeping my connections alive for long periods of time. I've never had any luck with TCP keepalives, so I don't bother with them.
      This is exactly the setting that isn't doing me any good. I need something more substantial than the plain keep-alive. I need something the server thinks is a real key press.
      I'll confirm what diotalevi said; that option doesn't do any good for me, either.

      Some nice ideas in this thread!


      Mike
        On some *nix OS I have found security policies that forcibly disconnect terminal sessions believed to be idle. In which case keep-alives don't offer any help. One way to fool the policy is to leave the terminal with a blank "read" request.

        prompt$ jobs
        <stuff running in background (or not, just camping)>
        prompt$ read
        <terminal (and policy) thinks we are waiting for user input and dare not end our session>

        When you return just hit enter, and you get your prompt back.

        This is also a great way to keep "screen" sessions from being killed based on policy idle timeouts.

        Of course your situation may be very different. Hope this helps, If not don't blame a guy for trying to help.