-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathsshlm
executable file
·159 lines (135 loc) · 6.25 KB
/
sshlm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#!/usr/bin/perl
# SSH line-mode wrapper by Matija Nalis <[email protected]> GPLv3+, started 2015-09-14
use warnings;
use strict;
use autodie;
use Term::ReadLine;
use IO::Handle;
use IO::Pty::Easy;
use Term::ReadKey;
$ENV{'PATH'} = '/usr/local/bin:/usr/bin:/bin';
my $HOTKEY = "\c]"; # to trigger line-mode
my $PROMPT = '[L] '; # readline prompt shown in line-mode
my $SSH = 'ssh'; # ssh client to execute (we're only wrapper for it!)
my $REMOTE_PS1_RE = qr/-lm[%#>\$]\h*($|\e.{1,5})/i; # regexp to match remote prompt, for auto-enter-linemode functionality (NEW_PS1 must set prompt to be matched by this)
my $NEW_PS1 = q{PS1='ssh-lm> '; _S=${0##*/}; _S=${_S#-*} ; echo Setting ssh-lm prompt for shell $_S; [ "$_S" = "zsh" ] && PS1='%m-lm%# '; [ "$_S" = "bash" ] && PS1='\h-lm\$ '; export PS1}; # command to execute on remote to force new PS1 prompt
my $AUTO_PS1_RE = qr/[%#>\$]\h{0,2}(\e.{1,5})?\h?$/i; # if ENV{SSHLM_AUTO} is defined, use this regexp to detect remote command line, and force NEW_PS1 automatically
#
# no user configurable parts below
#
$|=1;
my $TRY_AUTOMODE = $ENV{'SSHLM_AUTO'} || 0;
my $term = Term::ReadLine->new('sshlm');
die 'Need Term::ReadLine::Gnu installed' unless $term->ReadLine eq 'Term::ReadLine::Gnu';
my $readline_accept_key;
my $undo_local = 0;
sub accept_line_immed() {
my ($count, $key) = @_;
$readline_accept_key = chr($key);
$term->Attribs->{done} = 1;
return;
}
$term->add_defun('sshlm-accept-line-immed', \&accept_line_immed, ord $HOTKEY);
$term->parse_and_bind('"\C-c": sshlm-accept-line-immed');
$term->parse_and_bind('"\C-j": sshlm-accept-line-immed');
$term->parse_and_bind('"\C-m": sshlm-accept-line-immed');
$term->MinLine(undef); # disable autohistory
$term->addhistory($NEW_PS1) if !$TRY_AUTOMODE; # fake history for easy adding PS1 (if not in auto-add-prompt mode)
$term->read_init_file(); # otherwise add_defun() used in .inputrc won't be recognized
sub do_readline {
my ($prompt) = @_;
# while in readline, disable local handling of ctrl-C, ctrl-\ and ctrl-Z
local $SIG{INT} = 'IGNORE';
local $SIG{QUIT} = 'IGNORE';
#local $SIG{TSTP} = 'IGNORE';
$readline_accept_key = "\n"; # assume newline if "accept-line" called instead of "sshlm-accept-line-immed"
$undo_local = 1;
return $term->readline($prompt);
}
sub undo_readline {
my ($back, $erase) = @_;
print "\b" x ($back);
print " " x ($erase);
print "\b" x ($erase);
$undo_local = 0;
}
#my $OUT = $term->OUT || \*STDOUT;
my $linemode_active = 0;
my $last_history_added = '';
print "Use '^]' to toggle line mode.\n"; # FIXME hardcoded help
ReadMode 'ultra-raw';
my $pty = IO::Pty::Easy->new ( raw => 0 );
$pty->spawn($SSH, @ARGV);
# define filedescriptors on which we will wait
my $r_in='';
vec($r_in, fileno(STDIN), 1) = 1;
vec($r_in, $pty->fileno, 1) = 1;
MAIN: while ($pty->is_active) {
my $output = $pty->read(0);
if (defined $output) {
if ($undo_local) { # if this is first output after we've finished readline
# we erase from screen all the locally outputed text (including readline [L] prompt)
# as we're about to get copy which remote shell echos back to us (to avoid duplicate text)
my $p_len = length $PROMPT; # set to 0 instead of length($PROMPT) to leave local readline prompt intact
undo_readline ($p_len + $term->Attribs->{point}, $p_len + $term->Attribs->{end});
}
# should we try and setup own our PS1 automatically on connect?
if ($TRY_AUTOMODE and defined $AUTO_PS1_RE and $output =~ /$AUTO_PS1_RE/) {
$linemode_active = 2;
$TRY_AUTOMODE = 0; # we must execute only once at startup!
}
$linemode_active = 1 if defined $REMOTE_PS1_RE and $output =~ /$REMOTE_PS1_RE/;
print $output;
#print "[output ends a=$linemode_active] ";
#open LOG, '>>', '/tmp/sshlm.log'; print LOG "output=>$output<, a=$linemode_active]\n"; close LOG;
last MAIN if defined($output) && $output eq '';
}
my $all_input = '';
if ($linemode_active == 2) { # autodetected any remote prompt, and force our prompt
$all_input = $NEW_PS1 . "\n";
$linemode_active = 0;
}
READKEY: while (defined (my $input = ReadKey(-1))) {
if ($input eq $HOTKEY) { # hotkey activates line-mode
$linemode_active = ! $linemode_active;
next READKEY; # forget hotkey (do not store in buffer)
}
#print "[got input]";
#print $input;
$all_input .= $input;
}
PROCESS_KEYS: while ($all_input ne '' or $linemode_active) {
if ($all_input ne '') {
my $chars = $pty->write($all_input, 0);
$all_input = '';
last MAIN if defined($chars) && $chars == 0;
}
if ($linemode_active) {
ReadMode 'normal'; # enable local echo for readline
$all_input = do_readline($PROMPT);
ReadMode 'ultra-raw';
$linemode_active = 0; # turn off linemode once it was used
if (not defined $all_input) { # EOF in readline
$all_input = "\cd"; # send ctrl-d to remote side (hoping it *is* EOF char there)
next PROCESS_KEYS;
}
if ($all_input =~ /\S/ and $all_input ne $last_history_added) { # only add non-empty lines, and avoid duplicate lines
$term->addhistory($all_input); # do not put terminating char in history (newline, tab, hotkey, etc.)
$last_history_added = $all_input;
}
if ($readline_accept_key eq $HOTKEY) {
if ($all_input eq '') { # hotkey pressed as only char in readline: undo "[L]" prompt immedeately (as remote shell won't get anything it also won't output anything nor execute undo_readline()
my $p_len = length $PROMPT;
undo_readline ($p_len, $p_len);
}
} else {
$all_input .= $readline_accept_key; # add enter or tab to buffer, but not hotkey
}
next PROCESS_KEYS;
}
}
select ($_=$r_in, undef, $_=$r_in, undef); # infinite sleep until something comes on either on pty (output) or stdin (keyboard)
}
$pty->close;
ReadMode 'restore';
print "\n[sshlm exiting]\n";