package PACShell;
##################################################################
# 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 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 $_C = 1000000;
# 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 -> {_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} = 'CONNECTED';
$self -> {_STATUS_UPDATER} = 0;
$self -> {_LISTEN_COMMIT} = 1;
$self -> {_GUI} = undef;
$self -> {_KEYS_BUFFER} = '';
$self -> {_KEYS} = undef;
$self -> {_SAVE_KEYS} = 1;
$self -> {CONNECTED} = 1;
$self -> {ERROR} = '';
$self -> {_FULLSCREEN} = 0;
$self -> {_NEW_DATA} = 0;
$self -> {_FOCUSED} = 0;
$self -> {_UUID} = '/tmp/pac_PID' . $$ . '_n' . --$_C;
$self -> {_CMD} = '';
$self -> {_PID} = 0;
$self -> {_HISTORY} = '';
# Prepare the title
my $env = 'LOCAL';
my $conn = 'SHELL';
$self -> {_ENVIRONMENT} = $env;
$self -> {_CONNECTION} = $conn;
$self -> {_TITLE} = "$env - $conn";
# Build the GUI
_initGUI( $self ) or return 0;
# Setup callbacks
_setupCallbacks( $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'} = 1;
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};
# Start and fork our connector
if ( ! $$self{_GUI}{_VTE} -> fork_command( $$self{_CFG}{defaults}{'shell binary'}, [ $$self{_CFG}{defaults}{'shell binary'}, $$self{_CFG}{defaults}{'shell options'} ], undef, $$self{_CFG}{defaults}{'shell directory'}, 0, 0, 0 ) )
{
$$self{ERROR} = "ERROR: VTE could not fork command '$$self{_CFG}{defaults}{'shell binary'} $$self{_CFG}{defaults}{'shell options'}'!!";
return 0;
}
# ... and save its data
$PACMain::RUNNING{ $$self{'_UUID'} }{'start_time'} = time();
# 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} );
}
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;
}
$$self{CONNECTED} = 0;
Glib::Source -> remove( $$self{_STATUS_UPDATER} );
( $$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; }
# <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' )
{
$PACMain::FUNCS{_MAIN}{_GUI}{main} -> present;
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,
#{
# label => 'Split with TAB...',
# stockicon => 'gtk-zoom-fit',
# submenu => \@submenu_split,
# sensitive => scalar( @submenu_split )
#} );
}
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 } );
# 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(); }
} );
push( @vte_menu_items, { separator => 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 edit session
push( @vte_menu_items, { label => 'Edit session...', stockicon => 'gtk-edit', code => sub{ $PACMain::FUNCS{_EDIT} -> show( $$self{_ENVIRONMENT}, $$self{_CONNECTION} ); } } );
# Add restart session
push( @vte_menu_items, { separator => 1 } );
push( @vte_menu_items, { label => 'Restart session', stockicon => 'gtk-execute', sensitive => ! $$self{CONNECTED}, code => sub{ $$self{CONNECTED} = 1; $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 => '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 => '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;
# 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{_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{_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}{_TABIMG} = Gtk2::Image -> new_from_stock( $$self{CONNECTED} ? 'gtk-connect' : 'gtk-disconnect', 'menu' );
# $$self{_GUI}{_TABLBL} -> pack_start( $$self{_GUI}{_TABIMG}, 0, 1, 0 );
$$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;
}
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;
$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;
$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 );
$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 );
# 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}{'defaults'}{'text color'} ) );
$$self{_GUI}{_VTE} -> set_color_background( Gtk2::Gdk::Color -> parse( $$self{_CFG}{'defaults'}{'back color'} ) );
$$self{_GUI}{_VTE} -> set_font_from_string( $$self{_CFG}{'defaults'}{'terminal font'} );
$$self{_GUI}{_VTE} -> set_property( 'cursor-shape', $$self{_CFG}{'defaults'}{'cursor shape'} );
$$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_\-\.:\/' );
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;