diff --git a/Changes b/Changes index 0eaaef8..19731d9 100644 --- a/Changes +++ b/Changes @@ -4,6 +4,7 @@ Revision history for {{$dist->name}} - Include README within the repository, not just created tar.gz files - Add 'autoquit' setting to 'File' menu (Github issue #114) - Correct macro_hostname to be the FQDN of the server where cssh is being run (Github issue #116) +- Add in user defined macros 4.13.2_02 2019-01-14 Duncan Ferguson - Fix Getopt-Long minimum version diff --git a/README b/README index 5f9ac16..73e1809 100644 --- a/README +++ b/README @@ -83,8 +83,8 @@ OPTIONS Number of seconds to wait before closing finished terminal windows. --autoquit, -q - Toggle automatically quitting after the last client window has closed - (overriding the config file). + Toggle automatically quitting after the last client window has + closed (overriding the config file). --cluster-file '', -c '' Use supplied file as additional cluster file (see also "FILES"). @@ -223,6 +223,13 @@ KEY SHORTCUTS Alt-u Paste in the username for the connection + Alt-1 + Alt-2 + Alt-3 + Alt-4 + Run the matching user defined macro on the server and send the + output to the client + EXAMPLES Open up a session to 3 servers $ cssh server1 server2 server3 @@ -418,14 +425,52 @@ FILES Default key sequence to send username to client. See "KEY SHORTCUTS" for more information. + key_user_1 = Alt-1 + key_user_2 = Alt-2 + key_user_3 = Alt-3 + key_user_4 = Alt-4 + Default key sequence to send user defined macros to client. If + the matching macro_user_1 macro is undefined, the sequence is + passed straight to the terminal. See "KEY SHORTCUTS" for more + information. + macro_servername = %s macro_hostname = %h macro_username = %u macro_newline = %n macro_version = %v + macro_user_1 = %1 + macro_user_2 = %2 + macro_user_3 = %3 + macro_user_4 = %4 Change the replacement macro used when either using a 'Send' menu item, or when pasting text into the main console. + macro_user_1_command = + macro_user_2_command = + macro_user_3_command = + macro_user_4_command = + User defined macros - the macro is run through the shell on the + server and the output is sent to the client. For example, + + "macro_user_1_command=echo echo macro_user_1" + + would send the text C into the terminal session. + + "macro_user_1_command=env | grep CSSH" + + would send the CSSH environment variables to the client. + + The following environment variables are set in the shell of the + macro process + + "CSSH_SERVERNAME" + "CSSH_HOSTNAME" + "CSSH_USERNAME" + "CSSH_CONNECTION_STRING" + "CSSH_CONNECTION_PORT" + "CSSH_VERSION" + macros_enabled = yes Enable or disable macro replacement. Note: this affects all the "macro_*" variables above. diff --git a/lib/App/ClusterSSH/Config.pm b/lib/App/ClusterSSH/Config.pm index 7f17ced..eeb640b 100644 --- a/lib/App/ClusterSSH/Config.pm +++ b/lib/App/ClusterSSH/Config.pm @@ -49,6 +49,10 @@ my %default_config = ( key_macros_enable => "Alt-p", key_paste => "Control-v", key_username => "Alt-u", + key_user_1 => "Alt-1", + key_user_2 => "Alt-2", + key_user_3 => "Alt-3", + key_user_4 => "Alt-4", mouse_paste => "Button-2", auto_quit => "yes", auto_close => 5, @@ -103,6 +107,15 @@ my %default_config = ( macro_username => '%u', macro_newline => '%n', macro_version => '%v', + macro_user_1 => '%1', + macro_user_2 => '%2', + macro_user_3 => '%3', + macro_user_4 => '%4', + + macro_user_1_command => '', + macro_user_2_command => '', + macro_user_3_command => '', + macro_user_4_command => '', max_addhost_menu_cluster_items => 6, menu_send_autotearoff => 0, @@ -279,6 +292,15 @@ sub parse_config_file { if ( $read_config{terminal_font} ); $self->validate_args(%read_config); + + # Look at the user macros and if not set remove the hotkey for them + for my $i (qw/ 1 2 3 4 /) { + if ( ! $self->{"macro_user_${i}_command"} ) { + delete $self->{"key_user_${i}"}; + } + } + + return $self; } sub load_configs { diff --git a/lib/App/ClusterSSH/Getopt.pm b/lib/App/ClusterSSH/Getopt.pm index 06631e6..63c9831 100644 --- a/lib/App/ClusterSSH/Getopt.pm +++ b/lib/App/ClusterSSH/Getopt.pm @@ -559,6 +559,12 @@ would replace the with the client's name in each window.} output $self->loc(q{Retile all the client windows.}); output '=item ', $self->parent->config->{key_username}; output $self->loc(q{Paste in the username for the connection}); + output '=item ', $self->parent->config->{key_user_1} || 'Alt-1'; + output '=item ', $self->parent->config->{key_user_2} || 'Alt-2'; + output '=item ', $self->parent->config->{key_user_3} || 'Alt-3'; + output '=item ', $self->parent->config->{key_user_4} || 'Alt-4'; + output $self->loc(q{Run the matching user defined macro on the server and send the output to the client}); + output '=back'; output '=head1 ' . $self->loc('EXAMPLES'); @@ -768,15 +774,61 @@ If the external command is given a C<-L> option it should output a list of tags 'L' ); + output '=item key_user_1 = Alt-1'; + output '=item key_user_2 = Alt-2'; + output '=item key_user_3 = Alt-3'; + output '=item key_user_4 = Alt-4'; + output $self->loc( + q{Default key sequence to send user defined macros to client. If the matching [_2] macro is undefined, the sequence is passed straight to the terminal. See [_1] for more information.}, + 'L', 'L' + ); + output '=item macro_servername = %s'; output '=item macro_hostname = %h'; output '=item macro_username = %u'; output '=item macro_newline = %n'; output '=item macro_version = %v'; + output '=item macro_user_1 = %1'; + output '=item macro_user_2 = %2'; + output '=item macro_user_3 = %3'; + output '=item macro_user_4 = %4'; output $self->loc( q{Change the replacement macro used when either using a 'Send' menu item, or when pasting text into the main console.} ); + output '=item macro_user_1_command ='; + output '=item macro_user_2_command ='; + output '=item macro_user_3_command ='; + output '=item macro_user_4_command ='; + + output $self->loc( + q{User defined macros - the macro is run through the shell on the server and the output is sent to the client. For example,}, + ); + + output "C"; + output $self->loc( + q{ + would send the text [_1] into the terminal session. + }, + 'C' + ); + output "C"; + output $self->loc( + q{ + would send the CSSH environment variables to the client. + }, + ); + + output $self->loc("The following environment variables are set in the shell of the macro process"); + output '=over'; + output '=item C'; + output '=item C'; + output '=item C'; + output '=item C'; + output '=item C'; + output '=item C'; + output '=back'; + output '=item macros_enabled = yes'; output $self->loc( q{Enable or disable macro replacement. Note: this affects all the [_1] variables above.}, diff --git a/lib/App/ClusterSSH/Window/Tk.pm b/lib/App/ClusterSSH/Window/Tk.pm index 027dbbe..f6732fe 100644 --- a/lib/App/ClusterSSH/Window/Tk.pm +++ b/lib/App/ClusterSSH/Window/Tk.pm @@ -20,6 +20,8 @@ use Tk::Xlib; use Tk::ROText; require Tk::Dialog; require Tk::LabEntry; +use Symbol qw/ gensym /; +use IPC::Open3; use X11::Protocol 0.56; use X11::Protocol::Constants qw/ Shift Mod5 ShiftMask /; use X11::Protocol::WM 29; @@ -593,17 +595,20 @@ sub substitute_macros { my $macro_servername = $self->config->{macro_servername}; ( my $servername = $svr ) =~ s/\s+//; $text =~ s!$macro_servername!$servername!xsmg; + $ENV{CSSH_SERVERNAME} = $servername; } { my $macro_hostname = $self->config->{macro_hostname}; my $hostname = hostfqdn(); $text =~ s!$macro_hostname!$hostname!xsmg; + $ENV{CSSH_HOSTNAME} = $hostname; } { my $macro_username = $self->config->{macro_username}; my $username = $servers{$svr}{username}; $username ||= getpwuid($UID); $text =~ s!$macro_username!$username!xsmg; + $ENV{CSSH_USERNAME} = $username; } { my $macro_newline = $self->config->{macro_newline}; @@ -613,6 +618,54 @@ sub substitute_macros { my $macro_version = $self->config->{macro_version}; my $version = $self->parent->VERSION; $text =~ s/$macro_version/$version/xsmg; + $ENV{CSSH_VERSION} = $version; + } + + $ENV{CSSH_CONNECTION_STRING} = $servers{$svr}{connect_string}; + $ENV{CSSH_CONNECTION_PORT} = $servers{$svr}{port}; + + # Set up environment variables in the macro environment + for my $i (qw/ 1 2 3 4 / ) { + my $macro_user_command = 'macro_user_'.$i.'_command'; + my $macro_user = $self->config->{'macro_user_'.$i}; + + next unless $text =~ $macro_user; + if( ! $self->config->{ $macro_user_command } ) { + $text =~ s/$macro_user//xsmg; + next; + } + + my $cmd = $self->config->{ $macro_user_command }; + + local $SIG{CHLD} = undef; + + my $stderr_fh = gensym; + my $stdout_fh = gensym; + my $child_pid = eval { open3(undef, $stdout_fh, $stderr_fh, $cmd) }; + + if (my $err=$@) { + # error message is hardcoded into open3 - tidy it up a little for our users + $err=~ s/ at .*//; + $err=~ s/open3: //; + $err =~ s/( failed)/' $1/; + $err =~ s/(exec of) /$1 '/; + warn "Macro failure for '$macro_user_command': $err"; + next; + } + waitpid($child_pid, 0); + my $cmd_rc = $? >> 8; + + my @stdout = <$stdout_fh>; + my @stderr = <$stderr_fh>; + + if ( $cmd_rc > 0 || @stderr ){ + warn "Macro failure for '$macro_user_command'",$/; + warn "Exited with error output:: @stderr" if @stderr; + warn "Exited with non-zero return code: $cmd_rc", $/ if $cmd_rc; + } else { + #$self->send_text_to_all_servers( $stdout ); + return join('', @stdout); + } } return $text; @@ -1545,6 +1598,18 @@ sub key_event { $self->send_text_to_all_servers( $self->config->{macro_username} ) if ( $hotkey eq "key_username" ); + $self->send_text_to_all_servers( + $self->config->{macro_user_1} ) + if ( $hotkey eq "key_user_1" ); + $self->send_text_to_all_servers( + $self->config->{macro_user_2} ) + if ( $hotkey eq "key_user_2" ); + $self->send_text_to_all_servers( + $self->config->{macro_user_3} ) + if ( $hotkey eq "key_user_3" ); + $self->send_text_to_all_servers( + $self->config->{macro_user_4} ) + if ( $hotkey eq "key_user_4" ); $self->add_host_by_name() if ( $hotkey eq "key_addhost" ); $self->retile_hosts("force") diff --git a/t/15config.t b/t/15config.t index 99c6873..f1dd29b 100644 --- a/t/15config.t +++ b/t/15config.t @@ -52,6 +52,10 @@ Readonly::Hash my %default_config => { key_macros_enable => "Alt-p", key_paste => "Control-v", key_username => "Alt-u", + key_user_1 => "Alt-1", + key_user_2 => "Alt-2", + key_user_3 => "Alt-3", + key_user_4 => "Alt-4", mouse_paste => "Button-2", auto_quit => "yes", auto_close => 5, @@ -109,6 +113,15 @@ Readonly::Hash my %default_config => { macro_username => '%u', macro_newline => '%n', macro_version => '%v', + macro_user_1 => '%1', + macro_user_2 => '%2', + macro_user_3 => '%3', + macro_user_4 => '%4', + + macro_user_1_command => '', + macro_user_2_command => '', + macro_user_3_command => '', + macro_user_4_command => '', max_addhost_menu_cluster_items => 6, menu_send_autotearoff => 0, @@ -188,6 +201,9 @@ $expected{screen_reserve_left} = 100; $expected{screen_reserve_right} = 100; $expected{screen_reserve_top} = 100; $expected{screen_reserve_bottom} = 160; + +# Note: the parse_config here removes the key_user_x entries +delete( $expected{"key_user_$_"} ) for (qw/ 1 2 3 4 /); trap { $config = $config->parse_config_file( $file, ); }; @@ -328,6 +344,9 @@ open( my $csshrc, '>', $ENV{HOME} . '/.csshrc' ); print $csshrc 'auto_quit = no', $/; close($csshrc); $expected{auto_quit} = 'no'; + +# Note: the load_configs here removes the key_user_x entries +delete( $expected{"key_user_$_"} ) for (qw/ 1 2 3 4 /); $config = App::ClusterSSH::Config->new(); trap { $config->load_configs(); @@ -565,11 +584,23 @@ key_macros_enable=Alt-p key_paste=Control-v key_quit=Alt-q key_retilehosts=Alt-r +key_user_1=Alt-1 +key_user_2=Alt-2 +key_user_3=Alt-3 +key_user_4=Alt-4 key_username=Alt-u lang=en macro_hostname=%h macro_newline=%n macro_servername=%s +macro_user_1=%1 +macro_user_1_command= +macro_user_2=%2 +macro_user_2_command= +macro_user_3=%3 +macro_user_3_command= +macro_user_4=%4 +macro_user_4_command= macro_username=%u macro_version=%v macros_enabled=yes diff --git a/t/15config.t.file3 b/t/15config.t.file3 index 3c96051..8e1f7ef 100644 --- a/t/15config.t.file3 +++ b/t/15config.t.file3 @@ -16,6 +16,10 @@ key_paste=Control-v key_quit=Control-q key_retilehosts=Alt-r key_username=Alt-n +key_user_1=Alt-1 +key_user_2=Alt-2 +key_user_3=Alt-3 +key_user_4=Alt-4 max_host_menu_items=30 method=ssh mouse_paste=Button-2