package PACTerminal;
##################################################################
# This file is part of PAC( Perl Auto Connector)
#
# Copyright (C) 2010 David Torrejon Vaquerizas
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
###################################################################
$|++;
###################################################################
# Import Modules
# Standard
use strict;
use warnings;
use FindBin qw ( $RealBin $Bin $Script );
use YAML qw ( LoadFile DumpFile );
use POSIX qw ( strftime );
use File::Copy;
use Encode;
#use Data::Dumper;
# GTK2
use Gtk2 '-init';
use Gtk2::Ex::Simple::List;
# Gnome2 - VTE
use Gnome2::Vte;
# PAC modules
use PACUtils;
# END: Import Modules
###################################################################
###################################################################
# Define GLOBAL CLASS variables
my $APPNAME = $PACUtils::APPNAME;
my $APPVERSION = $PACUtils::APPVERSION;
my $APPICON = $RealBin . '/res/pac_terminal64x64.png';
my $PERL_BIN = '/usr/bin/perl';
my $PAC_CONN = $RealBin . '/lib/pac_conn';
my $_C = 1;
# END: Define GLOBAL CLASS variables
###################################################################
###################################################################
# START: Define CLASS methods
# CONSTRUCTOR: build GUI and setup callbacks
sub new
{
my $class = shift;
my $self = {};
$self -> {_CFG} = shift;
$self -> {_ENVIRONMENT} = shift;
$self -> {_CONNECTION} = shift;
$self -> {_NOTEBOOK} = shift;
$self -> {_NOTEBOOKWINDOW} = shift;
$self -> {_TABBED} = $self -> {_CFG}{'defaults'}{'open connections in tabs'};
$self -> {_SPLIT} = 0;
$self -> {_SPLIT_VPANE} = 0;
$self -> {_POST_SPLIT} = 0;
$self -> {_CLUSTER} = '';
$self -> {_LAST_STATUS} = '';
$self -> {_STATUS_UPDATER} = 0;
$self -> {_LISTEN_COMMIT} = 1;
$self -> {_GUI} = undef;
$self -> {_KEYS_BUFFER} = '';
$self -> {_KEYS} = undef;
$self -> {_SAVE_KEYS} = 1;
$self -> {CONNECTED} = 0;
$self -> {ERROR} = '';
$self -> {_FULLSCREEN} = 0;
$self -> {_NEW_DATA} = 0;
$self -> {_FOCUSED} = 0;
$self -> {_UUID} = '/tmp/pac_PID' . $$ . '_n' . ++$_C;
if ( $self -> {_CFG}{'defaults'}{'save session logs'} )
{
my $time = strftime "%Y%m%d_%H%M%S", localtime;
$self -> {_LOGFILE} = $self -> {_CFG}{'defaults'}{'session logs folder'} . "/PAC_[$$self{_ENVIRONMENT}_$$self{_CONNECTION}]_[$time].txt";
}
else
{
$self -> {_LOGFILE} = $$self{_UUID} . '.txt';
}
$self -> {_TMPCFG} = $$self{_UUID} . '.yml';
$self -> {_CTRLFILE} = $$self{_UUID} . '.ctrl';
$self -> {_SCREENSHOT} = $$self{_CFG}{environments}{ $$self{_ENVIRONMENT} }{ $$self{_CONNECTION} }{screenshot};
$self -> {_CMD} = '';
$self -> {_PID} = 0;
$self -> {_HISTORY} = '';
$self -> {_TEXT} = ();
# Prepare the title
my $env = $self -> {_ENVIRONMENT};
my $conn = $self -> {_CONNECTION};
$self -> {_TITLE} = $self -> {_CFG}{'environments'}{$env}{$conn}{'title'} || "$env - $conn";
# Build the GUI
_initGUI( $self ) or return 0;
# Setup callbacks
_setupCallbacks( $self );
%{ $$self{_METHODS} } = _getMethods( $self );
$PACMain::RUNNING{$$self{'_UUID'}}{'environment'} = $env;
$PACMain::RUNNING{$$self{'_UUID'}}{'connection'} = $conn;
$PACMain::RUNNING{$$self{'_UUID'}}{'terminal'} = $self;
$PACMain::RUNNING{$$self{'_UUID'}}{'is_shell'} = 0;
bless( $self, $class );
return $self;
}
# DESTRUCTOR
sub DESTROY
{
my $self = shift;
undef $self;
return 1;
}
# Launch connection
sub start
{
my $self = shift;
my $env = $$self{_ENVIRONMENT};
my $conn = $$self{_CONNECTION};
# Check for pre-connection commands execution
_wPrePostExec( $self, 'local before');
$$self{_CFG}{'tmp'}{'log file'} = $$self{_LOGFILE};
$$self{_CFG}{'tmp'}{'ctrl file'} = $$self{_CTRLFILE};
# Make sure that control file exists and is empty (for 'Restart session')
open F, ">$$self{_CTRLFILE}" or die "ERROR: Could not restart control file '$$self{_CTRLFILE}'!! ($!)";
close F;
# Dump non-persistent configuration into temporal file for 'pac_conn'
DumpFile( $$self{_TMPCFG}, $$self{_CFG} );
# Delete the oldest auto-saved session log
$self -> {_CFG}{'defaults'}{'save session logs'} and _deleteOldestSessionLog( $self -> {_CFG}, $env, $conn );
# Start and fork our connector
if ( ! $$self{_GUI}{_VTE} -> fork_command( $PERL_BIN, [ 'perl', $PAC_CONN, $$self{_TMPCFG}, $env, $conn ], undef, '', 0, 0, 0 ) )
{
$$self{ERROR} = "ERROR: VTE could not fork command '$PAC_CONN $$self{_TMPCFG} $env $conn'!!";
return 0;
}
# ... and save its data
$PACMain::RUNNING{ $$self{'_UUID'} }{'start_time'} = time();
# Prepare control file for reading
my $fh = $$self{_UUID};
{
no strict 'refs';
open( $fh, $$self{_CTRLFILE} ) or die "ERROR: Could not open $$self{_CTRLFILE} for reading: $!";
use strict 'refs';
}
# Initialize some progressbar's variables
my $expected = 0;
my $total = 0;
foreach my $exp ( @{ $$self{_CFG}{'environments'}{$env}{$conn}{'expect'} } ) { next unless ( $$exp{'active'} // 0 ); ++$total; }
# Create a progressbar and add it to GUI's bottombox
$$self{_GUI}{pb} = Gtk2::ProgressBar -> new();
$$self{_GUI}{bottombox} -> pack_start( $$self{_GUI}{pb}, 0, 1, 0 );
$$self{_GUI}{pb} -> show;
my $pulse = 1;
# Finally, set a timer to read from control file (as file's already open, every file read will be incremental)
$$self{_STATUS_UPDATER} = Glib::Timeout -> add( 300, sub
{
if ( $$self{_TABBED} )
{
my $check_gui = $PACMain::RUNNING{$$self{_UUID}}{terminal}{_SPLIT} ? $PACMain::RUNNING{$$self{_UUID}}{terminal}{_SPLIT_VPANE} : $PACMain::RUNNING{$$self{_UUID}}{terminal}{_GUI}{_VBOX};
$$self{_FOCUSED} = $check_gui -> get_child_visible;
$$self{_NEW_DATA} &&= ! $$self{_FOCUSED};
$PACMain::RUNNING{$PACMain::RUNNING{$$self{_UUID}}{terminal}{_SPLIT}}{terminal}{_FOCUSED} = $check_gui -> get_child_visible if $PACMain::RUNNING{$$self{_UUID}}{terminal}{_SPLIT};
$PACMain::RUNNING{$PACMain::RUNNING{$$self{_UUID}}{terminal}{_SPLIT}}{terminal}{_NEW_DATA} &&= ! $PACMain::RUNNING{$PACMain::RUNNING{$$self{_UUID}}{terminal}{_SPLIT}}{terminal}{_FOCUSED} if $PACMain::RUNNING{$$self{_UUID}}{terminal}{_SPLIT};
my $conn_color = $$self{_NEW_DATA} ? $$self{_CFG}{defaults}{'new data color'} : $$self{_CFG}{defaults}{'connected color'};
my $disconn_color = $$self{_CFG}{defaults}{'disconnected color'};
my $rem_conn_color = $PACMain::RUNNING{$$self{_SPLIT}}{terminal}{_NEW_DATA} ? $$self{_CFG}{defaults}{'new data color'} : $$self{_CFG}{defaults}{'connected color'} if $$self{_SPLIT};
if ( $$self{_SPLIT} )
{
$self -> {_GUI} -> {_TABLBL} -> {_LABEL} -> set_markup( '<span foreground="' . ( $self -> {CONNECTED} ? $conn_color : $disconn_color ) . '">' . $self -> {_TITLE} . '</span> + <span foreground="' . ( $PACMain::RUNNING{$self -> {_SPLIT}} -> {terminal} -> {CONNECTED} ? $rem_conn_color : $disconn_color ) . '">' . $PACMain::RUNNING{$self -> {_SPLIT}} -> {terminal} -> {_TITLE} . '</span>' );
}
else
{
my $lbl_txt = $self -> {_GUI} -> {_TABLBL} -> {_LABEL} -> get_text;
$self -> {_GUI} -> {_TABLBL} -> {_LABEL} -> set_markup( '<span foreground="' . ( $self -> {CONNECTED} ? $conn_color : $disconn_color ) . '">' . $self -> {_TITLE} . '</span>' );
}
}
else
{
defined $$self{_WINDOWTERMINAL} and $$self{_WINDOWTERMINAL} -> set_icon_name( $$self{CONNECTED} ? 'gtk-connect' : 'gtk-disconnect' );
}
# Control CLUSTER status
if ( $$self{_CLUSTER} ne '' )
{
$$self{_GUI}{status} -> push( 0, "[ IN CLUSTER: $$self{_CLUSTER} ]" . ' - Status: ' . $$self{_LAST_STATUS} );
}
else
{
$$self{_GUI}{status} -> push( 0, ' - Status: ' . $$self{_LAST_STATUS} );
}
( $pulse && $$self{_GUI}{pb} -> get_property( 'visible' ) ) and $$self{_GUI}{pb} -> pulse;
# Control connection feedback
while ( my $line = <$fh> )
{
chomp $line;
$$self{_LAST_STATUS} = $line;
$$self{_HISTORY} .= __( $line ) . "\n";
$$self{_GUI}{status} -> set_property( 'tooltip-markup', $$self{_HISTORY} );
$$self{_GUI}{pb} -> set_property( 'tooltip-markup', $$self{_HISTORY} );
if ( $line eq 'CONNECTED' )
{
$$self{CONNECTED} = 1;
$$self{_GUI}{pb} -> destroy;
$PACMain::FUNCS{_CLUSTER} -> _updateGUI;
$self -> _updateCFG;
}
#elsif ( ( $line eq 'DISCONNECTED' ) || ( $line =~ /^CLOSE:(.+)/go ) || ( $line =~ /^TIMEOUT:(.+)/go ) )
elsif ( ( $line eq 'DISCONNECTED' ) || ( $line =~ /^CLOSE:(.+)/go ) || ( $line =~ /^TIMEOUT:(.+)/go ) || ( $line =~ /^(connect\(\) failed with error '.+')$/go ) )
{
$expected = 0;
$$self{CONNECTED} = 0;
$$self{ERROR} = $1 // '';
$$self{_GUI}{pb} -> destroy;
$PACMain::FUNCS{_CLUSTER} -> _updateGUI;
$self -> _updateCFG;
}
elsif ( $line =~ /^EXPECT:WAITING:(.+)/go )
{
$pulse = 0;
$$self{_GUI}{pb} -> set_fraction( ++$expected / $total );
}
elsif ( $line =~ /^SPAWNED:'(.+)'\s*\(PID:(\d+)\)$/go )
{
$$self{_CMD} = $1;
$$self{_PID} = $2;
}
else
{
$$self{_GUI}{pb} -> pulse unless ( $$self{CONNECTED} && $$self{_GUI}{pb} -> get_property( 'visible' ) );
}
}
return 1;
} );
return 1;
}
# Stop and close GUI
sub stop
{
my $self = shift;
my $force = shift // 0;
my $deep = shift // 0;
my $env = $self -> {_ENVIRONMENT};
my $conn = $self -> {_CONNECTION};
# First of all, save THIS page's widget (to prevent closing a not selected tab)
my $p_widget = $$self{_GUI}{_VBOX};
# May be user wants to close without confirmation...
if ( ( ! $force ) && ( $self -> {CONNECTED} ) )
{
# Ask for confirmation
_wConfirm( $$self{GUI}{_VBOX}, "Are you sure you want to CLOSE '" . ( $$self{_SPLIT} ? 'this Splitted TAB' : $$self{_TITLE} ) . "'?" ) or return 1;
# Check for post-connection commands execution
$$self{CONNECTED} and _wPrePostExec( $self, 'local after');
}
elsif ( ! $force )
{
# Check for post-connection commands execution
$$self{CONNECTED} and _wPrePostExec( $self, 'local after');
}
# Send any configured keypress to close the forked binary
if ( $$self{CONNECTED} && defined $$self{_METHODS}{ $$self{_CFG}{'environments'}{$env}{$conn}{'method'} }{'escape'} )
{
foreach my $esc ( @{ $$self{_METHODS}{ $$self{_CFG}{'environments'}{$env}{$conn}{'method'} }{'escape'} } )
{
$$self{_GUI}{_VTE} -> feed_child( $esc );
}
}
$$self{CONNECTED} = 0;
Glib::Source -> remove( $$self{_STATUS_UPDATER} );
unlink( $$self{_TMPCFG} );
unlink( $$self{_CTRLFILE} );
unlink( $$self{_LOGFILE} ) unless $self -> {_CFG}{'defaults'}{'save session logs'};
( $$self{_SPLIT} && $deep ) and $PACMain::RUNNING{$$self{_SPLIT}}{terminal} -> stop( 1, 0 );
# Finish the GUI
if ( $$self{_TABBED} )
{
my $p_num = -1;
if ( $$self{_SPLIT} )
{
$p_num = $$self{_NOTEBOOK} -> page_num( $p_widget -> get_parent );
}
else
{
$p_num = $$self{_NOTEBOOK} -> page_num( $p_widget );
}
# Skip destruction if this tab does not exists after having answered to _wConfirm
$$self{_NOTEBOOK} -> remove_page( $p_num ) if $p_num >= 0;
}
else
{
$$self{_WINDOWTERMINAL} -> destroy;
}
delete $PACMain::RUNNING{$$self{_UUID}};
$PACMain::FUNCS{_CLUSTER} -> _updateGUI;
# And delete ourselves
$$self{_GUI} = undef;
undef $self;
return 1;
}
# END: Define CLASS methods
###################################################################
###################################################################
# START: Private functions definitions
sub _setupCallbacks
{
my $self = shift;
my $env = $self -> {_ENVIRONMENT};
my $conn = $self -> {_CONNECTION};
# Capture keypresses on VTE
$$self{_GUI}{_VTE} -> signal_connect( 'key_press_event' => sub
{
my ( $widget, $event ) = @_;
my $keyval = '' . ( $event -> keyval );
my $state = '' . ( $event -> state );
#print "TERMINAL KEYPRESS:*$state*$keyval*" . chr($keyval) . "*\n";
if ( ! $event -> state )
{
# F11 --> [un]fullscreen window
if ( $keyval == 65480 )
{
if ( $$self{_FULLSCREEN} )
{
$$self{_GUI}{_VBOX} -> window -> unfullscreen;
$$self{_FULLSCREEN} = 0;
}
else
{
$$self{_TABBED} and $self -> _tabToWin;
$$self{_GUI}{_VBOX} -> window -> fullscreen;
$$self{_FULLSCREEN} = 1;
}
return 1;
}
return 0;
}
# Capture only keypresses with modifiers (ctrl, alt, etc.)
############################################
# Generic VTE keystrokes
# <Ctrl><Shift>
if ( ( $event -> state >= 'control-mask' ) && ( $event -> state >= 'shift-mask' ) )
{
# C --> COPY
if ( chr( $keyval ) eq 'C' ) { $$self{_GUI}{_VTE} -> copy_clipboard(); return 1; }
# V --> PASTE
elsif ( chr( $keyval ) eq 'V' ) { $$self{_GUI}{_VTE} -> paste_clipboard(); return 1; }
# w --> Close terminal
elsif ( chr( $keyval ) eq 'W' ) { $self -> stop( undef, 1 ); return 1; }
# q --> Close PAC
elsif ( chr( $keyval ) eq 'Q' ) { $PACMain::FUNCS{_MAIN} -> _quitProgram; return 1; }
}
# <Ctrl>
elsif ( $event -> state == 'control-mask' )
{
# F4 --> CLOSE current tab
if ( ( $self -> {_TABBED} ) and ( $keyval == 65473 ) ) { $self -> stop( undef, 1 ); return 1; }
# F3 --> FIND in text buffer
if ( $keyval == 65472 ) { _wFindInTerminal( $self ); return 1; }
# <ins> --> COPY
if ( $keyval == 65379 ) { $$self{_GUI}{_VTE} -> copy_clipboard(); return 1; }
}
# <Shift>
elsif ( $event -> state == 'shift-mask' )
{
# <ins> --> PASTE
if ( $keyval == 65379 ) { $$self{_GUI}{_VTE} -> paste_clipboard(); return 1; }
}
# <Alt>
elsif ( $event -> state == 'mod1-mask' )
{
# c --> Show PAC Main Connections Window
if ( ( chr( $keyval ) eq 'c' ) || ( chr( $keyval ) eq 'n' ) )
{
$PACMain::FUNCS{_MAIN} -> _showConnectionsList;
return 1;
}
# e --> Show PAC Main Edit Connection Window
if ( chr( $keyval ) eq 'e' )
{
$PACMain::FUNCS{_EDIT} -> show( $$self{_ENVIRONMENT}, $$self{_CONNECTION} );
return 1;
}
# h --> Show Command History Window
if ( chr( $keyval ) eq 'h' )
{
$self -> _wHistory if $$self{_CFG}{'defaults'}{'record command history'};
return 1;
}
return 0;
}
return 0;
} );
# Right mouse mouse on VTE
$$self{_GUI}{_VTE} -> signal_connect( 'button_press_event' => sub
{
my ( $widget, $event ) = @_;
if ( $event -> button eq 2 )
{
$$self{_GUI}{_VTE} -> grab_focus;
return 0;
}
elsif ( $event -> button ne 3 )
{
return 0;
}
my @vte_menu_items;
# Show a popup with the opened tabs (if tabbed!!)
if ( $$self{_TABBED} )
{
my @submenu_goto;
my @submenu_split_h;
my @submenu_split_v;
foreach my $uuid ( keys %PACMain::RUNNING )
{
my $i = $$self{_NOTEBOOK} -> page_num( $PACMain::RUNNING{$uuid}{terminal}{_SPLIT} ? $PACMain::RUNNING{$uuid}{terminal}{_SPLIT_VPANE} : $PACMain::RUNNING{$uuid}{terminal}{_GUI}{_VBOX} );
next if ( $uuid eq $$self{_UUID} || $PACMain::RUNNING{$uuid}{terminal}{_TITLE} eq 'Info ' || $i < 0 );
push( @submenu_goto,
{
label => "$i: $PACMain::RUNNING{$uuid}{terminal}{_TITLE}",
code => sub { $$self{_NOTEBOOK} -> set_current_page( $i ); }
} );
next if ( $$self{_SPLIT} || $PACMain::RUNNING{$uuid}{terminal}{_SPLIT} ) || ( ! $PACMain::RUNNING{$uuid}{terminal}{_TABBED} );
push( @submenu_split_h,
{
label => "$i: $PACMain::RUNNING{$uuid}{terminal}{_TITLE}",
code => sub { $self -> _split( $uuid ); }
} );
push( @submenu_split_v,
{
label => "$i: $PACMain::RUNNING{$uuid}{terminal}{_TITLE}",
code => sub { $self -> _split( $uuid, 1 ); }
} );
}
@submenu_goto = sort { $$a{label} cmp $$b{label} } @submenu_goto;
@submenu_split_h = sort { $$a{label} cmp $$b{label} } @submenu_split_h;
@submenu_split_v = sort { $$a{label} cmp $$b{label} } @submenu_split_v;
push( @vte_menu_items,
{
label => 'Goto TAB',
stockicon => 'gtk-jump-to',
submenu => \@submenu_goto,
sensitive => scalar( @submenu_goto )
} );
push( @vte_menu_items, { separator => 1 } );
push( @vte_menu_items, { label => 'Detach TAB to a new Window', stockicon => 'gtk-fullscreen', code => sub { _tabToWin( $self ); return 1; } } );
if ( $$self{_SPLIT} )
{
push( @vte_menu_items,
{
label => 'Unsplit to new TAB',
stockicon => 'gtk-zoom-fit',
code => sub { $self -> _unsplit; }
} );
}
else
{
push( @vte_menu_items,
{
label => 'Split',
stockicon => 'gtk-zoom-fit',
sensitive => scalar( @submenu_split_h ) && scalar( @submenu_split_v ),
submenu =>
[
{
label => 'Horizontally with TAB',
stockicon => 'gtk-zoom-fit',
submenu => \@submenu_split_h,
sensitive => scalar( @submenu_split_h )
},
{
label => 'Vertically with TAB',
stockicon => 'gtk-zoom-fit',
submenu => \@submenu_split_v,
sensitive => scalar( @submenu_split_v )
}
]
} );
}
push( @vte_menu_items, { separator => 1 } );
}
else
{
push( @vte_menu_items, { label => 'Attach Window to main TAB bar', stockicon => 'gtk-leave-fullscreen', code => sub { _winToTab( $self ); return 1; } } );
push( @vte_menu_items, { separator => 1 } );
}
# Prepare the "Add to Cluster" submenu...
my @submenu_cluster;
my %clusters;
push( @submenu_cluster,
{
label => 'New Cluster...',
stockicon => 'gtk-new',
code => sub {
my $cluster = _wEnterValue( $self, 'Enter a name for the <b>New Cluster</b>' );
( ( ! defined $cluster ) || ( $cluster =~ /^\s*$/go ) ) and return 1;
$PACMain::FUNCS{_CLUSTER} -> addToCluster( $$self{_UUID}, $cluster );
}
} );
foreach my $uuid ( keys %PACMain::RUNNING )
{
next unless ( $PACMain::RUNNING{$uuid}{terminal}{_CLUSTER} ne '' );
$clusters{$PACMain::RUNNING{$uuid}{terminal}{_CLUSTER}}{total}++;
$clusters{$PACMain::RUNNING{$uuid}{terminal}{_CLUSTER}}{connections} .= "$PACMain::RUNNING{$uuid}{environment} - $PACMain::RUNNING{$uuid}{connection}\n";
}
foreach my $cluster ( sort { $a cmp $b } keys %clusters )
{
my $tmp = $cluster;
push( @submenu_cluster,
{
label => "$cluster ( $clusters{$cluster}{total} terminals connected )",
tooltip => $clusters{$cluster}{connections},
sensitive => $cluster ne $$self{_CLUSTER},
code => sub { $PACMain::FUNCS{_CLUSTER} -> addToCluster( $$self{_UUID}, $tmp ); }
} );
}
push( @vte_menu_items,
{
label => ( $$self{_CLUSTER} eq '' ? 'Add' : 'Change' ) . ' to Cluster',
stockicon => 'gtk-add',
sensitive => 1,
submenu => \@submenu_cluster
} );
push( @vte_menu_items,
{
label => 'Remove from Cluster',
stockicon => 'gtk-delete',
sensitive => $$self{_CLUSTER} ne '',
code => sub { $PACMain::FUNCS{_CLUSTER} -> delFromCluster( $$self{_UUID}, $$self{_CLUSTER} ); }
} );
push( @vte_menu_items,
{
label => 'Cluster Admin...',
stockicon => 'gtk-justify-fill',
sensitive => 1,
code => sub { $PACMain::FUNCS{_CLUSTER} -> show; }
} );
push( @vte_menu_items, { separator => 1 } );
# Show the list of REMOTE commands to execute
my @cmd_remote_sub_menu;
foreach my $hash ( @{ $self -> {_CFG}{'environments'}{$env}{$conn}{'macros'} } )
{
my $cmd = ref( $hash ) ? $$hash{txt} : $hash;
my $confirm = ref( $hash ) ? $$hash{confirm} : 0;
next unless $cmd ne '';
push( @cmd_remote_sub_menu,
{
label => $cmd,
sensitive => $$self{CONNECTED},
stockicon => $confirm ? 'gtk-dialog-question' : '',
code => sub { $self -> _execute( 'remote', $cmd, $confirm ) }
} );
}
push( @vte_menu_items,
{
label => 'Remote commands',
stockicon => 'gtk-execute',
submenu => \@cmd_remote_sub_menu,
sensitive => ( $#cmd_remote_sub_menu >= 0 )
} );
# Show the list of LOCAL commands to execute
my @cmd_local_sub_menu;
foreach my $hash ( @{ $self -> {_CFG}{'environments'}{$env}{$conn}{'local connected'} } )
{
my $cmd = ref( $hash ) ? $$hash{txt} : $hash;
my $confirm = ref( $hash ) ? $$hash{confirm} : 0;
next unless $cmd ne '';
push( @cmd_local_sub_menu,
{
label => $cmd,
stockicon => $confirm ? 'gtk-dialog-question' : '',
code => sub { $self -> _execute( 'local', $cmd, $confirm ) }
} );
}
push( @vte_menu_items,
{
label => 'Local commands',
stockicon => 'gtk-execute',
submenu => \@cmd_local_sub_menu,
sensitive => ( $#cmd_local_sub_menu >= 0 )
} );
push( @vte_menu_items, { separator => 1 } );
# Copy
push( @vte_menu_items,
{
label => 'Copy',
stockicon => 'gtk-copy',
sensitive => $$self{_GUI}{_VTE} -> get_has_selection(),
code => sub { $$self{_GUI}{_VTE} -> copy_clipboard(); }
} );
# Paste
push( @vte_menu_items,
{
label => 'Paste',
stockicon => 'gtk-paste',
sensitive => $$self{_GUI}{_VTE} -> get_clipboard( Gtk2::Gdk-> SELECTION_CLIPBOARD ) -> wait_is_text_available(),
code => sub { $$self{_GUI}{_VTE} -> paste_clipboard(); }
} );
# Add find string
push( @vte_menu_items, { separator => 1 } );
push( @vte_menu_items, { label => 'Find...', stockicon => 'gtk-find', code => sub { $self -> _wFindInTerminal; return 1; } } );
# Add show command history
push( @vte_menu_items, { label => 'Command History...', stockicon => 'gtk-orientation-landscape', sensitive => $$self{_CFG}{'defaults'}{'record command history'}, code => sub{ $self -> _wHistory; } } );
# Add save session log
push( @vte_menu_items, { label => 'Save session log...', stockicon => 'gtk-save', code => sub{ $self -> _saveSessionLog; } } );
# Add edit session
push( @vte_menu_items, { label => 'Edit session...', stockicon => 'gtk-edit', code => sub{ $PACMain::FUNCS{_EDIT} -> show( $$self{_ENVIRONMENT}, $$self{_CONNECTION} ); } } );
# Add change temporary tab label
push( @vte_menu_items, { label => 'Temporary TAB Label change...', stockicon => 'gtk-edit', code => sub
{
# Prepare the input window
my $new_label = _wEnterValue(
$PACMain::FUNCS{_MAIN}{_GUI}{main},
"<b>Temporaly renaming label '" . __( $$self{_TITLE} ) . "'</b>",
'Enter the new temporal label:',
$$self{_TITLE}
);
$$self{_TITLE} = $new_label if ( ( defined $new_label ) && ( $new_label !~ /^\s*$/go ) );
} } );
# Add take screenshot
push( @vte_menu_items, { label => 'Take Screenshot', stockicon => 'gtk-media-record', code => sub
{
_screenshot( $$self{_GUI}{_VBOX}, $$self{_SCREENSHOT} );
_scale( $$self{_SCREENSHOT}, 400, 300, 'keep aspect ratio' ) -> save( $$self{_SCREENSHOT}, 'jpeg' );
$PACMain::FUNCS{_MAIN} -> _updateGUIPreferences;
} } );
# Add restart session
push( @vte_menu_items, { separator => 1 } );
push( @vte_menu_items,
{
label => 'Terminal',
stockicon => 'gtk-terminal',
sensitive => 1,
submenu =>
[
{
label => 'Reset',
stockicon => 'gtk-refresh',
sensitive => 1,
code => sub{ $$self{_GUI}{_VTE} -> reset( 1, 0 ) ; }
},
{
label => 'Reset and clear',
stockicon => 'gtk-refresh',
sensitive => 1,
code => sub{ $$self{_GUI}{_VTE} -> reset( 1, 1 ) ; }
}
]
} );
push( @vte_menu_items, { label => 'Restart session', stockicon => 'gtk-execute', sensitive => ! $$self{CONNECTED}, code => sub{ $self -> start; } } );
# Add a submenu with available connections
push( @vte_menu_items, { separator => 1 } );
push( @vte_menu_items, { label => 'New connection', stockicon => 'gtk-connect', submenu => &_menuAvailableConnections } );
# Add close terminal
push( @vte_menu_items, { separator => 1 } );
push( @vte_menu_items, { label => 'Close terminal', stockicon => 'gtk-close', code => sub{ $self -> stop( undef, 1 ); } } );
_wPopUpMenu( \@vte_menu_items, $event );
return 1;
} );
# Capture mouse selection on VTE
$$self{_GUI}{_VTE} -> signal_connect( 'selection_changed' => sub { $$self{_GUI}{_VTE} -> copy_clipboard(); return 0; } );
$$self{_GUI}{_VTE} -> signal_connect( 'commit' => sub { $$self{_CFG}{'defaults'}{'record command history'} and $self -> _saveHistory( $_[1] ); $self -> _clusterCommit( @_ ); } );
$$self{_GUI}{_VTE} -> signal_connect( 'contents_changed' => sub { $$self{_NEW_DATA} = ! $$self{_FOCUSED}; } );
# Depending on terminal creation (window/tab), process connection closing differently
if ( ! $self -> {_TABBED} )
{
# Capture window close
$self -> {_WINDOWTERMINAL} -> signal_connect( 'delete_event' => sub { $self -> stop( undef, 1 ); return 1; } );
}
# Append VTE's connection finalization with CLOSE event
$$self{_GUI}{_VTE} -> signal_connect ( child_exited => sub
{
$$self{_LAST_STATUS} = 'DISCONNECTED';
# Give some time for GLib's Timer to read all the data from the control file
sleep 1/2;
# Check for post-connection commands execution
_wPrePostExec( $self, 'local after' );
# Update 'CONNECTED' status
$$self{CONNECTED} = 0;
# Remove the timer that controls the status bar
#Glib::Source -> remove( $$self{_STATUS_UPDATER} );
# And close if so is configured
$self -> stop( undef, 1 ) if $$self{_CFG}{'defaults'}{'close terminal on disconnect'};
return 1;
} );
return 1;
}
sub _clusterCommit
{
my ( $self, $terminal, $string, $int ) = @_;
return 1 unless $$self{_LISTEN_COMMIT} && ( $$self{_CLUSTER} ne '' ) && $$self{CONNECTED};
$$self{_LISTEN_COMMIT} = 0;
foreach my $uuid ( keys %PACMain::RUNNING )
{
next if ( ! $PACMain::RUNNING{$uuid}{terminal}{CONNECTED} ) || ( $PACMain::RUNNING{$uuid}{terminal}{_CLUSTER} ne $$self{_CLUSTER} ) || ( $PACMain::RUNNING{$uuid}{terminal}{_UUID} eq $$self{_UUID} );
$PACMain::RUNNING{$uuid}{terminal}{_LISTEN_COMMIT} = 0;
$PACMain::RUNNING{$uuid}{terminal}{_GUI}{_VTE} -> feed_child( $string );
$PACMain::RUNNING{$uuid}{terminal}{_LISTEN_COMMIT} = 1;
}
$$self{_LISTEN_COMMIT} = 1;
return 1;
}
sub _saveHistory
{
my ( $self, $string ) = @_;
return 1 unless $$self{_SAVE_KEYS};
my @chars = split( '', $string );
if ( ord( $chars[ $#chars ] ) == 13 )
{
if ( ( $$self{_KEYS_BUFFER} =~ /^\s*$/go ) && ( scalar( @chars ) <= 1 ) )
{
$$self{_KEYS_BUFFER} = '';
return 1;
}
pop( @chars );
$$self{_KEYS_BUFFER} .= join( '', @chars );
$$self{_KEYS}{ $$self{_KEYS_BUFFER} } = time;
$$self{_KEYS_BUFFER} = '';
}
else
{
$$self{_KEYS_BUFFER} .= $string if ord( $string ) >= 32;
}
return 1;
}
sub _tabToWin
{
my $self = shift;
my $env = $self -> {_ENVIRONMENT};
my $conn = $self -> {_CONNECTION};
my $tabs = $self -> {_NOTEBOOK};
my $i = $$self{_SPLIT} ? $$self{_GUI}{_SPLIT_VPANE} : $$self{_NOTEBOOK} -> page_num( $$self{_GUI}{_VBOX} );
$self -> {_WINDOWTERMINAL} = Gtk2::Window -> new();
if ( $$self{_SPLIT_VPANE} )
{
$$self{_SPLIT_VPANE} -> reparent( $self -> {_WINDOWTERMINAL} );
$PACMain::RUNNING{$$self{_SPLIT}}{terminal}{_TABBED} = 0;
}
else
{
$$self{_GUI}{_VBOX} -> reparent( $self -> {_WINDOWTERMINAL} );
}
$$self{_TABBED} = 0;
$$self{_WINDOWTERMINAL} -> set_title( $self -> {_CFG}{'environments'}{$env}{$conn}{'title'} );
$$self{_WINDOWTERMINAL} -> set_icon_name( $$self{CONNECTED} ? 'gtk-connect' : 'gtk-disconnect' );
$$self{_WINDOWTERMINAL} -> set_position( 'center' );
$$self{_WINDOWTERMINAL} -> set_size_request( 200, 100 );
$$self{_WINDOWTERMINAL} -> set_default_size( 640, 480 );
$$self{_WINDOWTERMINAL} -> present;
# Capture window close
$$self{_WINDOWTERMINAL} -> signal_connect( 'delete_event' => sub { $self -> stop( undef, 1 ); return 1; } );
$self -> _updateCFG;
$$self{_SPLIT} and $PACMain::RUNNING{$$self{_SPLIT}}{terminal}{_WINDOWTERMINAL} = $$self{_WINDOWTERMINAL};
$$self{_POST_SPLIT} and $self -> {_NOTEBOOK} -> remove_page( $i );
return 1;
}
sub _winToTab
{
my $self = shift;
my $env = $$self{_ENVIRONMENT};
my $conn = $$self{_CONNECTION};
my $tabs = $$self{_NOTEBOOK};
# Append this GUI to a new TAB (with an associated label && event_box -> image(close) button)
$$self{_GUI}{_TABLBL} = Gtk2::HBox -> new( 0, 0 );
my $eblbl1 = Gtk2::EventBox -> new();
$eblbl1 -> add( Gtk2::Image -> new_from_stock( 'gtk-close', 'menu' ) );
$eblbl1 -> signal_connect( 'button_release_event' => sub { $_[1] -> button != 1 and return 0; $self -> stop( undef, 1 ); } );
$$self{_GUI}{_TABLBL} -> pack_start( $eblbl1, 0, 1, 0 );
my $eblbl = Gtk2::EventBox -> new();
$$self{_GUI}{_TABLBL} -> pack_start( $eblbl, 0, 1, 0 );
$$self{_GUI}{_TABLBL}{_LABEL} = Gtk2::Label -> new( $$self{_TITLE} );
$eblbl -> add( $$self{_GUI}{_TABLBL}{_LABEL} );
$eblbl -> signal_connect( 'button_release_event' => sub
{
$_[1] -> button != 2 and return 0;
$self -> stop( undef, 1 );
} );
$$self{_GUI}{_TABLBL} -> show_all;
$tabs -> show;
if ( $$self{_SPLIT_VPANE} )
{
$$self{_SPLIT_VPANE} -> reparent( $tabs );
$tabs -> set_tab_label( $$self{_SPLIT_VPANE}, $$self{_GUI}{_TABLBL} );
$tabs -> set_tab_reorderable( $$self{_SPLIT_VPANE}, 1 );
$PACMain::RUNNING{$$self{_SPLIT}}{terminal}{_TABBED} = 1;
$PACMain::RUNNING{$$self{_SPLIT}}{terminal}{_WINDOWTERMINAL} -> destroy;
$PACMain::RUNNING{$$self{_SPLIT}}{terminal} -> _updateCFG unless $PACMain::RUNNING{$$self{_SPLIT}}{'is_shell'};
}
else
{
$$self{_GUI}{_VBOX} -> reparent( $tabs );
$tabs -> set_tab_label( $$self{_GUI}{_VBOX}, $$self{_GUI}{_TABLBL} );
$tabs -> set_tab_reorderable( $$self{_GUI}{_VBOX}, 1 );
$$self{_WINDOWTERMINAL} -> destroy;
}
$$self{_TABBED} = 1;
$self -> _updateCFG;
$tabs -> set_current_page( -1 );
return 1;
}
sub _split
{
my $self = shift;
my $uuid = shift;
my $vertical = shift // '0';
my $env = $self -> {_ENVIRONMENT};
my $conn = $self -> {_CONNECTION};
my $tabs = $self -> {_NOTEBOOK};
my $new_vpane = $vertical ? Gtk2::VPaned -> new : Gtk2::HPaned -> new;
$$self{_SPLIT_VPANE} = $new_vpane;
$PACMain::RUNNING{$uuid}{terminal}{_SPLIT_VPANE} = $new_vpane;
$$self{_GUI}{_VBOX} -> reparent( $new_vpane );
$PACMain::RUNNING{$uuid}{terminal}{_GUI}{_VBOX} -> reparent( $new_vpane );
# Append this GUI to a new TAB (with an associated label && event_box -> image(close) button)
$$self{_GUI}{_TABLBL} = Gtk2::HBox -> new( 0, 0 );
my $eblbl1 = Gtk2::EventBox -> new();
$eblbl1 -> add( Gtk2::Image -> new_from_stock( 'gtk-close', 'menu' ) );
$eblbl1 -> signal_connect( 'button_release_event' => sub { $_[1] -> button != 1 and return 0; $self -> stop( undef, 1 ); } );
$$self{_GUI}{_TABLBL} -> pack_start( $eblbl1, 0, 1, 0 );
my $eblbl = Gtk2::EventBox -> new();
$$self{_GUI}{_TABLBL} -> pack_start( $eblbl, 0, 1, 0 );
$$self{_GUI}{_TABLBL}{_LABEL} = Gtk2::Label -> new( $$self{_TITLE} . ' + ' . $PACMain::RUNNING{$uuid}{terminal}{_TITLE} );
$eblbl -> add( $$self{_GUI}{_TABLBL}{_LABEL} );
$eblbl -> signal_connect( 'button_release_event' => sub
{
$_[1] -> button != 2 and return 0;
$self -> stop( undef, 1 );
} );
$$self{_GUI}{_TABLBL} -> show_all;
$tabs -> append_page( $new_vpane, $$self{_GUI}{_TABLBL} );
$tabs -> show_all;
$tabs -> set_tab_reorderable( $new_vpane, 1 );
$tabs -> set_current_page( -1 );
$$self{_SPLIT} = $uuid;
$$self{_POST_SPLIT} = 0;
$PACMain::RUNNING{$uuid}{terminal}{_SPLIT} = $$self{_UUID};
$PACMain::RUNNING{$uuid}{terminal}{_POST_SPLIT} = 0;
$self -> _updateCFG;
$PACMain::RUNNING{$uuid}{terminal} -> _updateCFG unless $PACMain::RUNNING{$uuid}{'is_shell'};
return 1;
}
sub _unsplit
{
my $self = shift;
my $uuid = $$self{_SPLIT};
my $env = $self -> {_ENVIRONMENT};
my $conn = $self -> {_CONNECTION};
my $tabs = $self -> {_NOTEBOOK};
my $page = $$self{_NOTEBOOK} -> page_num( $$self{_SPLIT_VPANE} );
my $new_vbox_1 = Gtk2::VBox -> new( 0, 0 );
my $new_vbox_2 = Gtk2::VBox -> new( 0, 0 );
$$self{_GUI}{_VBOX} -> reparent( $new_vbox_1 );
$$self{_GUI}{_VBOX} = $new_vbox_1;
$$self{_TABBED} = 1;
$PACMain::RUNNING{$uuid}{terminal}{_GUI}{_VBOX} -> reparent( $new_vbox_2 );
$PACMain::RUNNING{$uuid}{terminal}{_GUI}{_VBOX} = $new_vbox_2;
$PACMain::RUNNING{$uuid}{terminal}{_TABBED} = 1;
# Append this GUI to a new TAB (with an associated label && event_box -> image(close) button)
$$self{_GUI}{_TABLBL} = Gtk2::HBox -> new( 0, 0 );
my $eblbl1 = Gtk2::EventBox -> new();
$eblbl1 -> add( Gtk2::Image -> new_from_stock( 'gtk-close', 'menu' ) );
$eblbl1 -> signal_connect( 'button_release_event' => sub { $_[1] -> button != 1 and return 0; $self -> stop( undef, 1 ); } );
$$self{_GUI}{_TABLBL} -> pack_start( $eblbl1, 0, 1, 0 );
my $eblbl = Gtk2::EventBox -> new();
$$self{_GUI}{_TABLBL} -> pack_start( $eblbl, 0, 1, 0 );
$$self{_GUI}{_TABLBL}{_LABEL} = Gtk2::Label -> new( $$self{_TITLE} );
$eblbl -> add( $$self{_GUI}{_TABLBL}{_LABEL} );
$eblbl -> signal_connect( 'button_release_event' => sub { $_[1] -> button != 2 and return 0; $self -> stop( undef, 1 ); } );
$$self{_GUI}{_TABLBL} -> show_all;
$tabs -> append_page( $new_vbox_1, $$self{_GUI}{_TABLBL} );
$tabs -> show_all;
$tabs -> set_tab_reorderable( $new_vbox_1, 1 );
# Append this GUI to a new TAB (with an associated label && event_box -> image(close) button)
$PACMain::RUNNING{$uuid}{terminal}{_GUI}{_TABLBL} = Gtk2::HBox -> new( 0, 0 );
my $eblbl3 = Gtk2::EventBox -> new();
$eblbl3 -> add( Gtk2::Image -> new_from_stock( 'gtk-close', 'menu' ) );
$eblbl3 -> signal_connect( 'button_release_event' => sub { $_[1] -> button != 1 and return 0; $self -> stop( undef, 1 ); } );
$PACMain::RUNNING{$uuid}{terminal}{_GUI}{_TABLBL} -> pack_start( $eblbl3, 0, 1, 0 );
my $eblbl2 = Gtk2::EventBox -> new();
$PACMain::RUNNING{$uuid}{terminal}{_GUI}{_TABLBL} -> pack_start( $eblbl2, 0, 1, 0 );
$PACMain::RUNNING{$uuid}{terminal}{_GUI}{_TABLBL}{_LABEL} = Gtk2::Label -> new( $PACMain::RUNNING{$uuid}{terminal}{_TITLE} );
$eblbl2 -> add( $PACMain::RUNNING{$uuid}{terminal}{_GUI}{_TABLBL}{_LABEL} );
$eblbl2 -> signal_connect( 'button_release_event' => sub { $_[1] -> button != 2 and return 0; $PACMain::RUNNING{$uuid}{terminal} -> stop( undef, 1 ); } );
$PACMain::RUNNING{$uuid}{terminal}{_GUI}{_TABLBL} -> show_all;
$tabs -> append_page( $new_vbox_2, $PACMain::RUNNING{$uuid}{terminal}{_GUI}{_TABLBL} );
$tabs -> show_all;
$tabs -> set_tab_reorderable( $new_vbox_2, 1 );
$$self{_SPLIT} = 0;
$PACMain::RUNNING{$uuid}{terminal}{_SPLIT} = 0;
$$self{_POST_SPLIT} = $new_vbox_1;
$PACMain::RUNNING{$uuid}{terminal}{_POST_SPLIT} = $new_vbox_1;
$$self{_SPLIT_VPANE} = 0;
$PACMain::RUNNING{$uuid}{terminal}{_SPLIT_VPANE} = 0;
$$self{_NOTEBOOK} -> remove_page( $page );
$tabs -> set_current_page( -1 );
$self -> _updateCFG;
$PACMain::RUNNING{$uuid}{terminal} -> _updateCFG unless $PACMain::RUNNING{$uuid}{'is_shell'};
return 1;
}
sub _initGUI
{
my $self = shift;
my $env = $self -> {_ENVIRONMENT};
my $conn = $self -> {_CONNECTION};
my $tabs = $self -> {_NOTEBOOK};
####
#### Building main window
####
# Create a GtkVBox and its child widgets:
$$self{_GUI}{_VBOX} = Gtk2::VBox -> new( 0, 0 );
#### $vbox 1st row: this will contain Gnome's VTE
# Create a GtkScrolledWindow,
my $sc = Gtk2::ScrolledWindow -> new();
$sc -> set_shadow_type( 'none' );
$sc -> set_policy( 'automatic', 'automatic' );
# , build a Gnome VTE Terminal,
$$self{_GUI}{_VTE} = Gnome2::Vte::Terminal -> new;
$$self{_GUI}{_VTE} -> set_size_request( 200, 100 );
# , add VTE to the scrolled window and...
$sc -> add( $$self{_GUI}{_VTE} );
# ... put this scrolled vte in $vbox
$$self{_GUI}{_VBOX} -> pack_start( $sc, 1, 1, 0 );
#### $vbox 2nd row: this will contain local/remote macros
# Create a GtkHBox and add it ot $vbox
$$self{_GUI}{_MACROSBOX} = Gtk2::HBox -> new( 0, 0 );
$$self{_GUI}{_VBOX} -> pack_start( $$self{_GUI}{_MACROSBOX}, 0, 1, 0 );
# Create a GtkButton and add it to $macrosbox
$$self{_GUI}{_BTNLOCALTERMINALEXEC} = Gtk2::Button -> new_with_mnemonic( '_Local' );
$$self{_GUI}{_BTNLOCALTERMINALEXEC} -> set_property( 'can_focus', 0 );
$$self{_GUI}{_BTNLOCALTERMINALEXEC} -> set_sensitive( 0 );
$$self{_GUI}{_BTNLOCALTERMINALEXEC} -> set_image( Gtk2::Image -> new_from_stock( 'gtk-execute', 'GTK_ICON_SIZE_SMALL_TOOLBAR' ) );
$$self{_GUI}{_BTNLOCALTERMINALEXEC} -> set_size_request( 60, 25 );
$$self{_GUI}{_MACROSBOX} -> pack_start( $$self{_GUI}{_BTNLOCALTERMINALEXEC}, 0, 1, 0 );
# Create a GtkComboBox and add it to $macrosbox
$$self{_GUI}{_CBLOCALEXECTERMINAL} = Gtk2::ComboBox -> new_text();
$$self{_GUI}{_CBLOCALEXECTERMINAL} -> set_property( 'can_focus', 0 );
$$self{_GUI}{_CBLOCALEXECTERMINAL} -> set_size_request( 200, -1 ); # Limit combobox hsize!!
$$self{_GUI}{_CBLOCALEXECTERMINAL} -> set_sensitive( 0 );
$$self{_GUI}{_CBLOCALEXECTERMINAL} -> set_size_request( 30, 25 );
$$self{_GUI}{_MACROSBOX} -> pack_start( $$self{_GUI}{_CBLOCALEXECTERMINAL}, 1, 1, 0 );
# Create a GtkComboBox and add it to $macrosbox
$$self{_GUI}{_CBMACROSTERMINAL} = Gtk2::ComboBox -> new_text();
$$self{_GUI}{_CBMACROSTERMINAL} -> set_property( 'can_focus', 0 );
$$self{_GUI}{_CBMACROSTERMINAL} -> set_size_request( 200, -1 ); # Limit combobox hsize!!
$$self{_GUI}{_CBMACROSTERMINAL} -> set_sensitive( 0 );
$$self{_GUI}{_CBMACROSTERMINAL} -> set_size_request( 60, 25 );
$$self{_GUI}{_MACROSBOX} -> pack_start( $$self{_GUI}{_CBMACROSTERMINAL}, 1, 1, 0 );
# Create a GtkButton and add it to $macrosbox
$$self{_GUI}{_BTNMACROSTERMINALEXEC} = Gtk2::Button -> new_with_mnemonic( '_Remote' );
$$self{_GUI}{_BTNMACROSTERMINALEXEC} -> set_property( 'can_focus', 0 );
$$self{_GUI}{_BTNMACROSTERMINALEXEC} -> set_sensitive( 0 );
$$self{_GUI}{_BTNMACROSTERMINALEXEC} -> set_image( Gtk2::Image -> new_from_stock( 'gtk-execute', 'GTK_ICON_SIZE_SMALL_TOOLBAR' ) );
$$self{_GUI}{_BTNMACROSTERMINALEXEC} -> set_size_request( 70, 25 );
$$self{_GUI}{_MACROSBOX} -> pack_start( $$self{_GUI}{_BTNMACROSTERMINALEXEC}, 0, 1, 0 );
$$self{_GUI}{_MACROSBOX} -> hide_all;
# bottombox will contain both progress and status bar
$$self{_GUI}{bottombox} = Gtk2::HBox -> new( 0, 0 );
$$self{_GUI}{_VBOX} -> pack_start( $$self{_GUI}{bottombox}, 0, 1, 0 );
# Create gtkstatusbar
$$self{_GUI}{status} = Gtk2::Statusbar -> new();
$$self{_GUI}{bottombox} -> pack_end( $$self{_GUI}{status}, 1, 1, 0 );
$$self{_GUI}{status} -> set_has_resize_grip( 1 );
# Set the number of scrollback lines
$$self{_GUI}{_VTE} -> set_scrollback_lines( $self -> {_CFG}{'defaults'}{'terminal scrollback lines'} );
############################################################################
# Check if this terminal should start in a new window or in a new tab/window
############################################################################
# New TAB:
if ( $$self{_TABBED} )
{
# Append this GUI to a new TAB (with an associated label && event_box -> image(close) button)
$$self{_GUI}{_TABLBL} = Gtk2::HBox -> new( 0, 0 );
my $eblbl1 = Gtk2::EventBox -> new();
$eblbl1 -> add( Gtk2::Image -> new_from_stock( 'gtk-close', 'menu' ) );
$eblbl1 -> signal_connect( 'button_release_event' => sub { $_[1] -> button != 1 and return 0; $self -> stop( undef, 1 ); } );
$$self{_GUI}{_TABLBL} -> pack_start( $eblbl1, 0, 1, 0 );
my $eblbl = Gtk2::EventBox -> new();
$$self{_GUI}{_TABLBL}{_LABEL} = Gtk2::Label -> new( $$self{_TITLE} );
$eblbl -> add( $$self{_GUI}{_TABLBL}{_LABEL} );
$eblbl -> signal_connect( 'button_release_event' => sub { $_[1] -> button != 2 and return 0; $self -> stop( undef, 1 ); } );
$$self{_GUI}{_TABLBL} -> pack_start( $eblbl, 0, 1, 0 );
$$self{_GUI}{_TABLBL} -> show_all;
$tabs -> append_page( $$self{_GUI}{_VBOX}, $$self{_GUI}{_TABLBL} );
$tabs -> show_all;
$tabs -> set_tab_reorderable( $$self{_GUI}{_VBOX}, 1 );
$tabs -> set_current_page( -1 );
$$self{_GUI}{_VTE} -> grab_focus;
$$self{_GUI}{_VTE} -> window -> show;
$$self{_NOTEBOOKWINDOW} -> maximize if $$self{_CFG}{'defaults'}{'start maximized'};
}
# New WINDOW:
else
{
# Build a new window,
$$self{_WINDOWTERMINAL} = Gtk2::Window -> new();
$$self{_WINDOWTERMINAL} -> set_title( $$self{_TITLE} . " : $APPNAME (v$APPVERSION)" );
$$self{_WINDOWTERMINAL} -> set_position( 'center' );
$$self{_WINDOWTERMINAL} -> set_size_request( 200, 100 );
$$self{_WINDOWTERMINAL} -> set_default_size( 640, 480 );
$$self{_WINDOWTERMINAL} -> maximize if $$self{_CFG}{'defaults'}{'start maximized'};
$$self{_WINDOWTERMINAL} -> set_icon_name( 'gtk-disconnect' );
$$self{_WINDOWTERMINAL} -> add( $$self{_GUI}{_VBOX} );
$$self{_WINDOWTERMINAL} -> show_all;
$$self{_WINDOWTERMINAL} -> present;
}
$$self{_GUI}{_VTE} -> set_color_foreground( Gtk2::Gdk::Color -> parse( $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'use personal settings'} ? $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'text color'} : $$self{_CFG}{'defaults'}{'text color'} ) );
$$self{_GUI}{_VTE} -> set_color_background( Gtk2::Gdk::Color -> parse( $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'use personal settings'} ? $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'back color'} : $$self{_CFG}{'defaults'}{'back color'} ) );
$$self{_GUI}{_VTE} -> set_font_from_string( $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'use personal settings'} ? $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'terminal font'} : $$self{_CFG}{'defaults'}{'terminal font'} );
$$self{_GUI}{_VTE} -> set_property( 'cursor-shape', $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'use personal settings'} ? $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'cursor shape'} : $$self{_CFG}{'defaults'}{'cursor shape'} );
if ( $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'use personal settings'} )
{
$$self{_GUI}{_VTE} -> set_background_transparent( $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'terminal transparency'} > 0 );
$$self{_GUI}{_VTE} -> set_background_saturation( $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'terminal transparency'} );
}
else
{
$$self{_GUI}{_VTE} -> set_background_transparent( $$self{_CFG}{'defaults'}{'terminal transparency'} > 0 );
$$self{_GUI}{_VTE} -> set_background_saturation( $$self{_CFG}{'defaults'}{'terminal transparency'} );
}
$$self{_GUI}{_VTE} -> set_word_chars( 'A-Za-z0-9_\-\.:\/' );
_updateCFG( $self );
return 1;
}
sub _saveSessionLog
{
my $self = shift;
my $time = strftime "%Y%m%d_%H%M%S", localtime;
my $new_file = $APPNAME . '-' . $self -> {_ENVIRONMENT} . '-' . $self -> {_CONNECTION} . '-' . $time . '.txt';
$new_file =~ s/\s+/_/go;
my $dialog = Gtk2::FileChooserDialog -> new (
'Select file to save session log',
undef,
'select-folder',
'gtk-ok' => 'GTK_RESPONSE_OK',
'gtk-cancel' => 'GTK_RESPONSE_CANCEL'
);
$dialog -> set_action( 'GTK_FILE_CHOOSER_ACTION_SAVE' );
$dialog -> set_do_overwrite_confirmation( 1 );
$dialog -> set_current_folder( $ENV{'HOME'} );
$dialog -> set_current_name( $new_file );
if ( $dialog -> run ne 'ok' )
{
$dialog->destroy;
return 1;
}
$new_file = $dialog -> get_filename();
$dialog -> destroy;
# Copy temporal log file to selected path
copy( $self -> {_LOGFILE}, $new_file );
return 1;
}
sub _execute
{
my $self = shift;
my $where = shift;
my $cmd = shift;
my $confirm = shift;
# Ask for confirmation
$confirm and ( _wConfirm( $$self{GUI}{_VBOX}, "Execute <b>'" . __( $cmd ) . "'</b> " . ( $where ne 'remote' ? 'LOCALLY' : 'REMOTELY' ) ) or return 1 );
# Replace '<GV:.+>' with user saved global variables for 'cmd' execution
while ( ( $cmd =~ /<GV:(.+)>/go ) && ( defined $self -> {_CFG}{'defaults'}{'global variables'}{ $1 } ) )
{
my $var = $1;
my $val = $self -> {_CFG}{'defaults'}{'global variables'}{ $var }{'value'} // '';
$cmd =~ s/<GV:$var>/$val/g;
}
# Replace '<V:#>' with user saved variables for 'cmd' execution
while ( ( $cmd =~ /<V:(\d+)>/go ) && ( defined $self -> {_CFG}{'environments'}{ $self -> {_ENVIRONMENT} }{ $self -> {_CONNECTION} }{'variables'}[ $1 ] ) )
{
my $var = $1;
my $val = $self -> {_CFG}{'environments'}{ $self -> {_ENVIRONMENT} }{ $self -> {_CONNECTION} }{'variables'}[ $var ] // '';
$cmd =~ s/<V:$var>/$val/g;
}
# Replace '<ENV:variable_name>' with it's corresponding environment variable
while ( ( $cmd =~ /<ENV\:(.+)>/go ) && ( defined $ENV{$1} ) )
{
my $var = $1;
my $env = $ENV{$var} // '';
$cmd =~ s/<ENV\:$var>/$env/g;
}
# Replace '<ASK:#>' with user provided data for 'cmd' execution
while ( $cmd =~ /<ASK:(\d+)>/go )
{
my $var = $1;
my $val = _wEnterValue( $self, "<b>Variable substitution #$var</b>" , $cmd ) // return 1;
$cmd =~ s/<ASK:$var>/$val/g;
}
# Finally, execute 'remote' or 'local'
if ( $where eq 'remote' )
{
$$self{_GUI}{_VTE} -> feed_child( $cmd . "\n" );
}
elsif ( $where eq 'local' )
{
system( $cmd . ' &' );
}
return 1;
}
sub _wPrePostExec
{
my $self = shift;
my $when = shift;
my $env = $$self{_ENVIRONMENT};
my $conn = $$self{_CONNECTION};
return 1 unless ( defined $env && defined $conn );
return 1 unless ( defined $$self{_CFG}{'environments'}{$env}{$conn}{$when} && scalar( @{ $$self{_CFG}{'environments'}{$env}{$conn}{$when} } ) );
# Build window
my %ppe = _ppeGUI( $self );
# Empty the connections tree
@{ $ppe{window}{gui}{treeview}{data} } = ();
# Populate the local executions tree
my $total = 0;
foreach my $hash ( @{ $self -> {_CFG}{'environments'}{$env}{$conn}{$when} } )
{
my $default = $$hash{'default'};
my $command = $$hash{'command'};
next if $command eq '';
push( @{ $ppe{window}{gui}{treeview}{data} }, [ $default, $command ] );
++$total;
}
return 1 unless $total;
# Change mouse cursor (to busy) in VTE window
$$self{_GUI}{_VTE} -> window -> set_cursor( Gtk2::Gdk::Cursor -> new( 'watch' ) );
# Now, prepare the local executions window...
# ... show it, AND stop until something clicked
$ppe{window}{data} -> show_all();
my $ok = $ppe{window}{data} -> run();
if ( $ok ne 'ok' )
{
$ppe{window}{data} -> hide();
# Reset mouse cursor in VTE window
$$self{_GUI}{_VTE} -> window -> set_cursor( Gtk2::Gdk::Cursor -> new( 'left-ptr' ) );
return 1;
}
# Check for commands selected for local execution
# Get total # of commands checked to be executed (for the progress bar)
my $t = 0;
foreach my $line ( @{ $ppe{window}{gui}{treeview}{data} } ) { my ( $def, $cmd ) = @{ $line }; $t += $def; }
# Change mouse cursor (to busy)
$ppe{window}{data} -> window -> set_cursor( Gtk2::Gdk::Cursor -> new( 'watch' ) );
$ppe{window}{data} -> set_sensitive( 0 );
my $i = 0;
foreach my $line ( @{ $ppe{window}{gui}{treeview}{data} } )
{
my ( $def, $cmd ) = @{ $line };
# Skip unchecked commands
next unless $def;
# Replace '<ENV:variable_name>' with it's corresponding environment variable
while ( ( $cmd =~ /<ENV\:(.+)>/go ) && ( defined $ENV{$1} ) )
{
my $var = $1;
my $env = $ENV{$var} // '';
$cmd =~ s/<ENV\:$var>/$env/g;
}
# Replace '<GV:.+>' with user saved global variables for 'cmd' execution
while ( ( $cmd =~ /<GV:(.+)>/go ) && ( defined $self -> {_CFG}{'defaults'}{'global variables'}{ $1 } ) )
{
my $var = $1;
my $val = $self -> {_CFG}{'defaults'}{'global variables'}{ $var }{'value'} // '';
$cmd =~ s/<GV:$var>/$val/g;
}
# Replace '<V:#>' with user saved variables for 'cmd' execution
while ( ( $cmd =~ /<V:(\d+)>/go ) && ( defined $self -> {_CFG}{'environments'}{ $self -> {_ENVIRONMENT} }{ $self -> {_CONNECTION} }{'variables'}[ $1 ] ) )
{
my $var = $1;
my $val = $self -> {_CFG}{'environments'}{ $self -> {_ENVIRONMENT} }{ $self -> {_CONNECTION} }{'variables'}[ $var ] // '';
$cmd =~ s/<V:$var>/$val/g;
}
# Make some update to progress bar
$ppe{window}{gui}{pb} -> set_text( 'Executing: ' . $cmd );
$ppe{window}{gui}{pb} -> set_fraction( ++$i / $t );
Gtk2 -> main_iteration while Gtk2 -> events_pending;
# Launch the local command
system( $cmd );
}
# Reset mouse cursor in VTE window
$$self{_GUI}{_VTE} -> window -> set_cursor( Gtk2::Gdk::Cursor -> new( 'left-ptr' ) );
# Reset progressbar status
$ppe{window}{gui}{pb} -> set_text( '' );
$ppe{window}{gui}{pb} -> set_fraction( 0 );
# Hide window
$ppe{window}{data} -> set_sensitive( 1 );
$ppe{window}{data} -> hide();
return 1;
sub _ppeGUI
{
my $self = shift;
my $env = $self -> {_ENVIRONMENT};
my $conn = $self -> {_CONNECTION};
my %w;
# Create the dialog window,
$w{window}{data} = Gtk2::Dialog -> new_with_buttons(
$self -> {_TITLE} . " : $APPNAME : Local execution",
undef,
'modal',
'gtk-ok' => 'ok',
'gtk-cancel' => 'cancel'
);
# and setup some dialog properties.
$w{window}{data} -> set_default_response( 'ok' );
$w{window}{data} -> set_position( 'center' );
$w{window}{data} -> set_icon_from_file( $APPICON );
$w{window}{data} -> set_size_request( 400, 300 );
$w{window}{data} -> set_resizable( 1 );
# Create frame
$w{window}{gui}{frame} = Gtk2::Frame -> new();
$w{window}{data} -> vbox -> pack_start( $w{window}{gui}{frame}, 1, 1, 0 );
$w{window}{gui}{frame} -> set_label( 'Select local command(s) to execute:' );
$w{window}{gui}{frame} -> set_border_width( 5 );
# Create a GtkScrolledWindow,
my $sct = Gtk2::ScrolledWindow -> new();
$w{window}{gui}{frame} -> add( $sct );
$sct -> set_shadow_type( 'none' );
$sct -> set_policy( 'automatic', 'automatic' );
# Create treeview
$w{window}{gui}{treeview} = Gtk2::Ex::Simple::List -> new_from_treeview (
Gtk2::TreeView -> new(),
' EXEC: ' => 'bool',
' LOCAL COMMAND :' => 'text'
);
$sct -> add( $w{window}{gui}{treeview} );
# Create progress bar
$w{window}{gui}{pb} = Gtk2::ProgressBar -> new();
$w{window}{data} -> vbox -> pack_start( $w{window}{gui}{pb}, 0, 1, 5 );
return %w;
}
}
sub _updateCFG
{
my $self = shift;
my $env = $$self{_ENVIRONMENT};
my $conn = $$self{_CONNECTION};
# Empty every 'remote' and 'local' command
for( my $i = 0; $i < 50; $i++ ) { $$self{_GUI}{_CBMACROSTERMINAL} -> remove_text( 0 ); }
for( my $i = 0; $i < 50; $i++ ) { $$self{_GUI}{_CBLOCALEXECTERMINAL} -> remove_text( 0 ); }
$$self{_GUI}{_BTNMACROSTERMINALEXEC} -> set_sensitive( $$self{CONNECTED} );
$$self{_GUI}{_CBMACROSTERMINAL} -> set_sensitive( $$self{CONNECTED} );
$$self{_GUI}{_BTNLOCALTERMINALEXEC} -> set_sensitive( 0 );
$$self{_GUI}{_CBLOCALEXECTERMINAL} -> set_sensitive( 0 );
$$self{_GUI}{_MACROSBOX} -> hide_all;
# Populate the macros (remote executions) combobox
foreach my $hash ( @{ $self -> {_CFG}{'environments'}{$env}{$conn}{'macros'} } )
{
my $cmd = ref( $hash ) ? $$hash{txt} : $hash;
my $confirm = ref( $hash ) ? $$hash{confirm} : 0;
next unless $cmd ne '';
$$self{_GUI}{_CBMACROSTERMINAL} -> append_text( ( $confirm ? 'CONFIRM: ' : '' ) . $cmd );
$$self{_GUI}{_MACROSBOX} -> show;
$$self{_GUI}{_BTNMACROSTERMINALEXEC} -> show_all;
$$self{_GUI}{_CBMACROSTERMINAL} -> show_all;
$$self{_GUI}{_BTNMACROSTERMINALEXEC} -> signal_handler_disconnect( $$self{_SIGNALS}{_BTNMACROSTERMINALEXEC} ) if $$self{_SIGNALS}{_BTNMACROSTERMINALEXEC};
$$self{_SIGNALS}{_BTNMACROSTERMINALEXEC} = $$self{_GUI}{_BTNMACROSTERMINALEXEC} -> signal_connect( 'clicked' => sub
{
return 1 if $$self{_GUI}{_CBMACROSTERMINAL} -> get_active == -1;
my $hash = $$self{_CFG}{'environments'}{$env}{$conn}{'macros'}[ $$self{_GUI}{_CBMACROSTERMINAL} -> get_active ];
my $cmd = ref( $hash ) ? $$hash{txt} : $hash;
my $confirm = ref( $hash ) ? $$hash{confirm} : 0;
$self -> _execute( 'remote', $cmd, $confirm );
return 1;
} );
}
# Populate the local executions combobox
foreach my $hash ( @{ $self -> {_CFG}{'environments'}{$env}{$conn}{'local connected'} } )
{
my $cmd = ref( $hash ) ? $$hash{txt} : $hash;
my $confirm = ref( $hash ) ? $$hash{confirm} : 0;
next unless $cmd ne '';
$$self{_GUI}{_BTNLOCALTERMINALEXEC} -> set_sensitive( 1 );
$$self{_GUI}{_CBLOCALEXECTERMINAL} -> set_sensitive( 1 );
$$self{_GUI}{_CBLOCALEXECTERMINAL} -> append_text( ( $confirm ? 'CONFIRM: ' : '' ) . $cmd );
$$self{_GUI}{_MACROSBOX} -> show;
$$self{_GUI}{_BTNLOCALTERMINALEXEC} -> show_all;
$$self{_GUI}{_CBLOCALEXECTERMINAL} -> show_all;
$$self{_GUI}{_BTNLOCALTERMINALEXEC} -> signal_handler_disconnect( $$self{_SIGNALS}{_BTNLOCALTERMINALEXEC} ) if $$self{_SIGNALS}{_BTNLOCALTERMINALEXEC};
$$self{_SIGNALS}{_BTNLOCALTERMINALEXEC} = $$self{_GUI}{_BTNLOCALTERMINALEXEC} -> signal_connect( 'clicked' => sub
{
return 1 if $$self{_GUI}{_CBLOCALEXECTERMINAL} -> get_active == -1;
my $hash = $$self{_CFG}{'environments'}{$env}{$conn}{'local connected'}[ $$self{_GUI}{_CBLOCALEXECTERMINAL} -> get_active ];
my $cmd = ref( $hash ) ? $$hash{txt} : $hash;
my $confirm = ref( $hash ) ? $$hash{confirm} : 0;
$self -> _execute( 'local', $cmd, $confirm );
return 1;
} );
}
# Update some VTE options
$$self{_GUI}{_VTE} -> set_color_foreground( Gtk2::Gdk::Color -> parse( $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'use personal settings'} ? $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'text color'} : $$self{_CFG}{'defaults'}{'text color'} ) );
$$self{_GUI}{_VTE} -> set_color_background( Gtk2::Gdk::Color -> parse( $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'use personal settings'} ? $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'back color'} : $$self{_CFG}{'defaults'}{'back color'} ) );
$$self{_GUI}{_VTE} -> set_font_from_string( $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'use personal settings'} ? $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'terminal font'} : $$self{_CFG}{'defaults'}{'terminal font'} );
$$self{_GUI}{_VTE} -> set_property( 'cursor-shape', $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'use personal settings'} ? $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'cursor shape'} : $$self{_CFG}{'defaults'}{'cursor shape'} );
if ( $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'use personal settings'} )
{
$$self{_GUI}{_VTE} -> set_background_transparent( $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'terminal transparency'} > 0 );
$$self{_GUI}{_VTE} -> set_background_saturation( $$self{_CFG}{environments}{$env}{$conn}{'terminal options'}{'terminal transparency'} );
}
else
{
$$self{_GUI}{_VTE} -> set_background_transparent( $$self{_CFG}{'defaults'}{'terminal transparency'} > 0 );
$$self{_GUI}{_VTE} -> set_background_saturation( $$self{_CFG}{'defaults'}{'terminal transparency'} );
}
return 1;
}
sub _wFindInTerminal
{
my $self = shift;
my $env = $self -> {_ENVIRONMENT};
my $conn = $self -> {_CONNECTION};
our $searching = 0;
our $stop = 0;
our %w;
defined $w{window} and return $w{window}{data} -> present;
# Create the 'windowFind' dialog window,
$w{window}{data} = Gtk2::Window -> new();
$w{window}{data} -> signal_connect( 'delete_event' => sub
{
$searching = 0;
$stop = 0;
$w{window}{data} -> destroy;
undef %w;
return 1;
} );
$w{window}{data} -> signal_connect( 'key_press_event' => sub
{
my ( $widget, $event ) = @_;
my $keyval = '' . ( $event -> keyval );
return 0 unless $keyval == 65307;
$w{window}{gui}{btnclose} -> activate();
return 1;
} );
# and setup some dialog properties.
$w{window}{data} -> set_title( $self -> {_TITLE} . " : $APPNAME : Find in Terminal" );
$w{window}{data} -> set_position( 'center' );
$w{window}{data} -> set_icon_from_file( $APPICON );
$w{window}{data} -> set_default_size( 600, 400 );
$w{window}{data} -> maximize;
$w{window}{data} -> set_resizable( 1 );
$w{window}{data} -> set_modal( 1 );
# Create an hbox
$w{window}{gui}{hboxmain} = Gtk2::HPaned -> new;
$w{window}{data} -> add( $w{window}{gui}{hboxmain} );
# Create a vbox
$w{window}{gui}{vbox} = Gtk2::VBox -> new( 0, 0 );
$w{window}{gui}{vbox} -> set_size_request( 300, 200 );
$w{window}{gui}{hboxmain} -> pack1( $w{window}{gui}{vbox}, 1, 0 );
# Create frame 1
$w{window}{gui}{frame1} = Gtk2::Frame -> new();
$w{window}{gui}{vbox} -> pack_start( $w{window}{gui}{frame1}, 0, 1, 0 );
$w{window}{gui}{frame1} -> set_label( ' Enter Regular Expression to look for: ' );
$w{window}{gui}{frame1} -> set_border_width( 5 );
$w{window}{gui}{hbox} = Gtk2::HBox -> new( 0, 0 );
$w{window}{gui}{frame1} -> add( $w{window}{gui}{hbox} );
$w{window}{gui}{hbox} -> set_border_width( 5 );
# Create 'find' image
$w{window}{gui}{img} = Gtk2::Image -> new_from_stock( 'gtk-find', 'dialog' );
$w{window}{gui}{hbox} -> pack_start( $w{window}{gui}{img}, 0, 1, 5 );
# Create search entry
$w{window}{gui}{entry} = Gtk2::Entry -> new();
$w{window}{gui}{hbox} -> pack_start( $w{window}{gui}{entry}, 1, 1, 0 );
$w{window}{gui}{entry} -> set_activates_default( 1 );
$w{window}{gui}{entry} -> has_focus( 1 );
# Create 'case sensitive search' checkbutton
$w{window}{gui}{cbCaseSensitive} = Gtk2::CheckButton -> new_with_label( 'Case sensitive' );
$w{window}{gui}{hbox} -> pack_start( $w{window}{gui}{cbCaseSensitive}, 0, 1, 0 );
$w{window}{gui}{cbCaseSensitive} -> set_active( 0 );
# Create "Search" button
$w{window}{gui}{btnfind} = Gtk2::Button -> new_from_stock( 'gtk-find' );
$w{window}{gui}{hbox} -> pack_start( $w{window}{gui}{btnfind}, 0, 1, 0 );
$w{window}{gui}{btnfind} -> signal_connect( 'clicked' => sub
{
if ( ! $searching )
{
$searching = 1;
$self -> _find;
$searching = 0;
}
else
{
$stop = 1;
}
} );
$w{window}{gui}{btnfind} -> can_default( 1 );
$w{window}{gui}{btnfind} -> grab_default();
# Create frame 2
$w{window}{gui}{frame2} = Gtk2::Frame -> new();
$w{window}{gui}{vbox} -> pack_start( $w{window}{gui}{frame2}, 1, 1, 0 );
$w{window}{gui}{frame2} -> set_label( ' Lines matching string: ' );
$w{window}{gui}{frame2} -> set_border_width( 5 );
# Create a GtkScrolledWindow,
my $sctxt = Gtk2::ScrolledWindow -> new();
$w{window}{gui}{frame2} -> add( $sctxt );
$sctxt -> set_shadow_type( 'none' );
$sctxt -> set_policy( 'automatic', 'automatic' );
$sctxt -> set_border_width( 5 );
# Create treefound
$w{window}{gui}{treefound} = Gtk2::Ex::Simple::List -> new_from_treeview (
Gtk2::TreeView -> new(),
' Line # ' => 'text',
' Line contents ' => 'text'
);
$w{window}{gui}{treefound} -> set_headers_visible( 1 );
$w{window}{gui}{treefound} -> set_grid_lines( 'both' );
$w{window}{gui}{treefound} -> get_selection -> set_mode( 'multiple' );
$w{window}{gui}{treefound} -> signal_connect( 'row_activated' => sub
{
my @index = $w{window}{gui}{treefound} -> get_selected_indices;
return unless scalar( @index ) == 1;
my $id = pop( @index );
$self -> _showLine( $w{window}{gui}{treefound}{data}[$id][0] );
return 1;
} );
# Put treefound into scrolledwindow
$sctxt -> add( $w{window}{gui}{treefound} );
# Put a separator
$w{window}{gui}{sep} = Gtk2::HSeparator -> new();
$w{window}{gui}{vbox} -> pack_start( $w{window}{gui}{sep}, 0, 1, 5 );
# Put a hbox to add copy/close buttons
$w{window}{gui}{hbtnbox} = Gtk2::HBox -> new();
$w{window}{gui}{vbox} -> pack_start( $w{window}{gui}{hbtnbox}, 0, 1, 0 );
$w{window}{gui}{hbtnbox} -> set_border_width( 5 );
# Put a button to copy selected rows to clipboard
$w{window}{gui}{btnCopy} = Gtk2::Button -> new_from_stock( 'gtk-copy' );
$w{window}{gui}{hbtnbox} -> pack_start( $w{window}{gui}{btnCopy}, 1, 1, 0 );
$w{window}{gui}{btnCopy} -> signal_connect( 'clicked' => sub
{
$$self{_GUI}{_VTE} -> get_clipboard( Gtk2::Gdk-> SELECTION_CLIPBOARD ) -> set_text (
join(
"\n",
map $w{window}{gui}{treefound}{data}[$_][1],
$w{window}{gui}{treefound} -> get_selected_indices
)
),
} );
# Put a 'close' button
$w{window}{gui}{btnclose} = Gtk2::Button -> new_from_stock( 'gtk-close' );
$w{window}{gui}{hbtnbox} -> pack_start( $w{window}{gui}{btnclose}, 0, 1, 0 );
$w{window}{gui}{btnclose} -> signal_connect( 'clicked' => sub
{
$w{window}{data} -> destroy;
undef %w;
return 1;
} );
# Create frame 3
$w{window}{gui}{frame3} = Gtk2::Frame -> new();
$w{window}{gui}{frame3} -> set_size_request( 200, 200 );
$w{window}{gui}{hboxmain} -> pack2( $w{window}{gui}{frame3}, 1, 0 );
$w{window}{gui}{frame3} -> set_label( ' Contents of current log: ' );
$w{window}{gui}{frame3} -> set_border_width( 5 );
$w{window}{gui}{scroll} = Gtk2::ScrolledWindow -> new();
$w{window}{gui}{frame3} -> add( $w{window}{gui}{scroll} );
$w{window}{gui}{scroll} -> set_policy( 'automatic', 'automatic' );
$w{window}{gui}{scroll} -> set_border_width( 5 );
$w{window}{buffer} = Gtk2::TextBuffer-> new;
$w{window}{gui}{text} = Gtk2::TextView -> new_with_buffer( $w{window}{buffer} );
$w{window}{gui}{text} -> set_editable( 0 );
$w{window}{gui}{text} -> modify_font( Pango::FontDescription->from_string( 'monospace' ) );
$w{window}{gui}{scroll} -> add( $w{window}{gui}{text} );
$w{window}{data} -> show_all();
# Load the contents of the textbuffer with the corresponding log file
open( F, $$self{_LOGFILE} ) or die( "ERROR: Could not open file '$$self{_LOGFILE}': $!" );
@{ $$self{_TEXT} } = <F>;
my $text = join( '', @{ $$self{_TEXT} } );
$text =~ s/\x1b\[\d*;?\d*m//go; # Delete the Escape sequences
$text =~ s/\cM//go; # Delete any Ctrl-M ( ^M ) character
close F;
$w{window}{buffer} -> set_text( encode( 'utf8', $text ) );
sub _showLine
{
my $self = shift;
my $line = shift;
--$line;
my $siter = $w{window}{buffer} -> get_iter_at_line( $line );
my $eiter = $w{window}{buffer} -> get_iter_at_line( $line );
$eiter -> forward_to_line_end;
$w{window}{buffer} -> select_range( $siter, $eiter );
$w{window}{gui}{text} -> scroll_to_iter( $siter, 0, 1, 0, 0.5 );
return 1;
}
sub _find
{
my $self = shift;
my $val = $w{window}{gui}{entry} -> get_chars( 0, -1 );
$w{window}{gui}{vbox} -> window -> set_cursor( Gtk2::Gdk::Cursor -> new( 'watch' ) );
$w{window}{gui}{hbtnbox} -> set_sensitive( 0 );
$w{window}{gui}{frame2} -> set_label( ' PLEASE, WAIT. SEARCHING... ' );
$w{window}{gui}{btnfind} -> set_label( 'STOP SEARCH' );
$w{window}{gui}{btnfind} -> set_image( Gtk2::Image -> new_from_stock( 'gtk-close', 'GTK_ICON_SIZE_BUTTON' ) );
# Empty previous found lines
@{ $w{window}{gui}{'treefound'}{data} } = ();
Gtk2 -> main_iteration while Gtk2 -> events_pending;
my %found;
my $i = 0;
my $l = 0;
foreach my $line ( @{ $$self{_TEXT} } )
{
chomp $line;
++$l;
last if $stop;
if ( ++$i >= 1000 )
{
$i = 0;
$w{window}{gui}{frame2} -> set_label( ' PLEASE, WAIT. Searching... (' . scalar( keys %found ) . " lines matching '$val' so far in $l processed lines) " );
Gtk2 -> main_iteration;
}
chomp $line;
if ( $w{window}{gui}{cbCaseSensitive} -> get_active() )
{
next unless $line =~ m/$val/g;
}
else
{
next unless $line =~ m/$val/gi;
}
$found{$l} = $line;
$found{$l} =~ s/\x1b\[\d*;?\d*m//go; # Delete the Escape sequences
$found{$l} =~ s/\n|\r|\f|\cM//go; # Delete the ctrl-M, new-line and similar sequences
}
if ( $stop )
{
# Update label text to announce that search was stopped
$w{window}{gui}{frame2} -> set_label( ' SEARCH WAS STOPPED WITH ' . scalar( keys %found ) . " LINES MATCHING '$val' SO FAR!! " );
Gtk2 -> main_iteration;
$stop = 0;
}
else
{
# Update label text with number of matches found
$w{window}{gui}{frame2} -> set_label( ' PLEASE, WAIT. Updating... (' . scalar( keys %found ) . " lines matching '$val') " );
Gtk2 -> main_iteration while Gtk2 -> events_pending;
# Update tree with the list of found lines
foreach my $line_num ( sort { $a <=> $b } keys %found ) { push( @{ $w{window}{gui}{treefound}{data} }, [ $line_num, $found{$line_num} ] ); }
}
$w{window}{gui}{vbox} -> window -> set_cursor( Gtk2::Gdk::Cursor -> new( 'left-ptr' ) );
$w{window}{gui}{hbtnbox} -> set_sensitive( 1 );
$w{window}{gui}{frame2} -> set_label( ' ' . scalar( keys %found ) . " lines matching '$val': " );
$w{window}{gui}{btnfind} -> set_label( 'Find' );
$w{window}{gui}{btnfind} -> set_image( Gtk2::Image -> new_from_stock( 'gtk-find', 'GTK_ICON_SIZE_BUTTON' ) );
$w{window}{gui}{entry} -> has_focus( 1 );
$w{window}{gui}{entry} -> grab_focus();
return 1;
}
return 1;
}
sub _wHistory
{
my $self = shift;
my $env = $self -> {_ENVIRONMENT};
my $conn = $self -> {_CONNECTION};
our %w;
defined $w{window} and return $w{window}{data} -> present;
# Create the 'windowFind' dialog window,
$w{window}{data} = Gtk2::Window -> new();
$w{window}{data} -> signal_connect( 'delete_event' => sub
{
$w{window}{data} -> destroy;
undef %w;
return 1;
} );
$w{window}{data} -> signal_connect( 'key_press_event' => sub
{
my ( $widget, $event ) = @_;
my $keyval = '' . ( $event -> keyval );
return 0 unless $keyval == 65307;
$w{window}{gui}{btnclose} -> activate();
return 1;
} );
# and setup some dialog properties.
$w{window}{data} -> set_title( $self -> {_TITLE} . " : $APPNAME : Command History" );
$w{window}{data} -> set_position( 'center' );
$w{window}{data} -> set_icon_from_file( $APPICON );
$w{window}{data} -> set_default_size( 600, 480 );
$w{window}{data} -> set_resizable( 1 );
$w{window}{data} -> set_modal( 1 );
# Create a vbox
$w{window}{gui}{vbox} = Gtk2::VBox -> new( 0, 0 );
$w{window}{data} -> add( $w{window}{gui}{vbox} );
# Create frame 1
$w{window}{gui}{frame1} = Gtk2::Frame -> new();
$w{window}{gui}{vbox} -> pack_start( $w{window}{gui}{frame1}, 1, 1, 0 );
$w{window}{gui}{frame1} -> set_label( ' Command History: ' );
$w{window}{gui}{frame1} -> set_border_width( 5 );
# Create a GtkScrolledWindow,
my $sctxt = Gtk2::ScrolledWindow -> new();
$w{window}{gui}{frame1} -> add( $sctxt );
$sctxt -> set_shadow_type( 'none' );
$sctxt -> set_policy( 'automatic', 'automatic' );
$sctxt -> set_border_width( 5 );
# Create treefound
$w{window}{gui}{treefound} = Gtk2::Ex::Simple::List -> new_from_treeview (
Gtk2::TreeView -> new(),
' Last Execution ' => 'text',
' Command ' => 'text'
);
$w{window}{gui}{treefound} -> set_headers_visible( 1 );
$w{window}{gui}{treefound} -> set_grid_lines( 'both' );
$w{window}{gui}{treefound} -> get_selection -> set_mode( 'single' );
foreach my $cmd ( sort { $$self{_KEYS}{$b} cmp $$self{_KEYS}{$a} } keys %{ $$self{_KEYS} } )
{
push( @{ $w{window}{gui}{treefound}{data} }, [ strftime( "%Y-%m-%d %H:%M:%S", localtime( $$self{_KEYS}{$cmd} ) ), $cmd ] );
}
$w{window}{gui}{treefound} -> signal_connect( 'row_activated' => sub { $w{window}{gui}{btnExec} -> clicked } );
# Put treefound into scrolledwindow
$sctxt -> add( $w{window}{gui}{treefound} );
# Put a separator
$w{window}{gui}{sep} = Gtk2::HSeparator -> new();
$w{window}{gui}{vbox} -> pack_start( $w{window}{gui}{sep}, 0, 1, 5 );
# Put a hbox to add exec/close buttons
$w{window}{gui}{hbtnbox} = Gtk2::HBox -> new();
$w{window}{gui}{vbox} -> pack_start( $w{window}{gui}{hbtnbox}, 0, 1, 0 );
$w{window}{gui}{hbtnbox} -> set_border_width( 5 );
# Put a button to execute selected row
$w{window}{gui}{btnExec} = Gtk2::Button -> new_from_stock( 'gtk-execute' );
$w{window}{gui}{hbtnbox} -> pack_start( $w{window}{gui}{btnExec}, 1, 1, 0 );
$w{window}{gui}{btnExec} -> signal_connect( 'clicked' => sub
{
my ( $selected ) = $w{window}{gui}{treefound} -> get_selected_indices;
return 1 unless defined $selected;
my $cmd = $w{window}{gui}{treefound}{data}[$selected][1];
$$self{_SAVE_KEYS} = 0;
$self -> _execute( 'remote', $cmd, 0 );
$$self{_KEYS}{$cmd} = time;
$$self{_SAVE_KEYS} = 1;
} );
# Put a 'close' button
$w{window}{gui}{btnclose} = Gtk2::Button -> new_from_stock( 'gtk-close' );
$w{window}{gui}{hbtnbox} -> pack_start( $w{window}{gui}{btnclose}, 0, 1, 0 );
$w{window}{gui}{btnclose} -> signal_connect( 'clicked' => sub
{
$w{window}{data} -> destroy;
undef %w;
return 1;
} );
$w{window}{data} -> show_all();
return 1;
}
# END: Private functions definitions
###################################################################
1;