| 1 |
rousseau |
3510 |
#!/usr/bin/perl -w |
| 2 |
lvictor |
3504 |
|
| 3 |
rousseau |
3510 |
# gscriptor: GTK interface to send APDU commands to a smart card |
| 4 |
rousseau |
3520 |
# Copyright (C) 2001 Lionel Victor, Ludovic Rousseau |
| 5 |
rousseau |
3626 |
# 2002-2003 Ludovic Rousseau |
| 6 |
rousseau |
3510 |
# |
| 7 |
|
|
# This program is free software; you can redistribute it and/or modify |
| 8 |
|
|
# it under the terms of the GNU General Public License as published by |
| 9 |
|
|
# the Free Software Foundation; either version 2 of the License, or |
| 10 |
|
|
# (at your option) any later version. |
| 11 |
|
|
# |
| 12 |
|
|
# This program is distributed in the hope that it will be useful, |
| 13 |
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 |
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 15 |
|
|
# GNU General Public License for more details. |
| 16 |
|
|
# |
| 17 |
|
|
# You should have received a copy of the GNU General Public License |
| 18 |
|
|
# along with this program; if not, write to the Free Software |
| 19 |
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 20 |
|
|
|
| 21 |
lvictor |
3504 |
# gscriptor uses libgtk-perl, please make sure it is correctly installed |
| 22 |
|
|
# on your system |
| 23 |
|
|
|
| 24 |
rousseau |
3659 |
# $Id: gscriptor,v 1.23 2003-11-08 17:07:02 rousseau Exp $ |
| 25 |
rousseau |
3626 |
|
| 26 |
lvictor |
3505 |
use strict; |
| 27 |
|
|
|
| 28 |
lvictor |
3504 |
use Gtk; |
| 29 |
|
|
use File::Basename; |
| 30 |
|
|
use Carp; |
| 31 |
rousseau |
3626 |
use Chipcard::PCSC; |
| 32 |
|
|
use Chipcard::PCSC::Card; |
| 33 |
lvictor |
3504 |
|
| 34 |
|
|
init Gtk; |
| 35 |
|
|
set_locale Gtk; |
| 36 |
|
|
|
| 37 |
|
|
my $strAppName = "gscriptor"; |
| 38 |
|
|
my $strConfigFileName = "$ENV{HOME}/.$strAppName"; |
| 39 |
|
|
my @ResultStruct; |
| 40 |
|
|
my %hConfig; |
| 41 |
|
|
|
| 42 |
|
|
# PCSC related variables |
| 43 |
rousseau |
3626 |
my $hContext = new Chipcard::PCSC (); |
| 44 |
|
|
die ("Can't create the Chipcard::PCSC object: $Chipcard::PCSC::errno\n") unless (defined $hContext); |
| 45 |
|
|
my $hCard = new Chipcard::PCSC::Card ($hContext); |
| 46 |
|
|
die ("Can't create the Chipcard::PCSC::Card object: $Chipcard::PCSC::errno\n") unless (defined $hContext); |
| 47 |
lvictor |
3504 |
|
| 48 |
|
|
############################### create widgets ############################### |
| 49 |
|
|
my $wndMain = new Gtk::Window ("toplevel"); |
| 50 |
|
|
my $txtScript = new Gtk::Text (); |
| 51 |
|
|
my $txtResult = new Gtk::Text (); |
| 52 |
|
|
my $rdbASCII = new Gtk::RadioButton("ASCII"); |
| 53 |
|
|
my $rdbHex = new Gtk::RadioButton("Hex", $rdbASCII); |
| 54 |
|
|
my $chkWrap = new Gtk::CheckButton ("Wrap lines"); |
| 55 |
|
|
my $btnRun = new Gtk::Button ("Run"); |
| 56 |
|
|
my $txtStatus = new Gtk::Entry(); |
| 57 |
|
|
|
| 58 |
|
|
|
| 59 |
|
|
############################## arrange widgets ############################### |
| 60 |
|
|
# arrange_widgets () is used to arrange widgets in the windows. |
| 61 |
|
|
# It allows to conveniently allocate temporary variables for the various |
| 62 |
|
|
# containers and the scrollbars, as well as the menu. |
| 63 |
|
|
# This function also sets some basic properties of the different |
| 64 |
|
|
# widgets used... configuration is therefore grouped in a single place. |
| 65 |
|
|
sub arrange_widgets () { |
| 66 |
|
|
$wndMain->set_title ("$strAppName"); |
| 67 |
|
|
|
| 68 |
|
|
$txtScript->set_editable(1); |
| 69 |
|
|
$txtScript->set_word_wrap(1); |
| 70 |
|
|
|
| 71 |
|
|
$txtResult->set_word_wrap(1); |
| 72 |
|
|
|
| 73 |
|
|
$txtStatus->set_sensitive(0); |
| 74 |
|
|
$txtStatus->set_editable(0); |
| 75 |
|
|
|
| 76 |
|
|
$chkWrap->set_active(1); |
| 77 |
|
|
$rdbHex->set_active(1); |
| 78 |
|
|
|
| 79 |
|
|
# create and bind toolbars |
| 80 |
|
|
my $vscScript = new Gtk::VScrollbar ($txtScript->vadj); |
| 81 |
|
|
my $vscResult = new Gtk::VScrollbar ($txtResult->vadj); |
| 82 |
|
|
|
| 83 |
|
|
# create and set tooltips |
| 84 |
|
|
my $tipScript = new Gtk::Tooltips(); |
| 85 |
|
|
my $tipResult = new Gtk::Tooltips(); |
| 86 |
|
|
my $tipASCII = new Gtk::Tooltips(); |
| 87 |
|
|
my $tipHex = new Gtk::Tooltips(); |
| 88 |
|
|
my $tipRun = new Gtk::Tooltips(); |
| 89 |
|
|
my $tipWrap = new Gtk::Tooltips(); |
| 90 |
|
|
$tipScript->set_tip ($txtScript, "list of APDUs to exchange"); |
| 91 |
|
|
$tipResult->set_tip ($txtResult, "result window"); |
| 92 |
|
|
$tipASCII->set_tip ($rdbASCII, "show ASCII data"); |
| 93 |
|
|
$tipHex->set_tip ($rdbHex, "show hexadecimal data"); |
| 94 |
|
|
$tipRun->set_tip ($btnRun, "run the current script"); |
| 95 |
rousseau |
3519 |
$tipWrap->set_tip ($chkWrap, "wrap long lines"); |
| 96 |
lvictor |
3504 |
|
| 97 |
|
|
# create and set the menu with the accel_table |
| 98 |
|
|
my @menu_items = ({ path => '/_File', |
| 99 |
|
|
type => '<Branch>' }, |
| 100 |
|
|
{ path => '/File/_New', |
| 101 |
|
|
accelerator => '<control>N', |
| 102 |
|
|
callback => \&NewScriptFile }, |
| 103 |
|
|
{ path => '/File/_Open', |
| 104 |
|
|
accelerator => '<control>O', |
| 105 |
|
|
callback => \&LoadScriptFile }, |
| 106 |
|
|
{ path => '/File/_Save', |
| 107 |
|
|
accelerator => '<control>S', |
| 108 |
|
|
callback => \&SaveScriptFile }, |
| 109 |
|
|
{ path => '/File/Save _As', |
| 110 |
|
|
callback => \&SaveAsScriptFile }, |
| 111 |
|
|
{ path => '/File/sep', |
| 112 |
|
|
type => '<Separator>' }, |
| 113 |
rousseau |
3517 |
{ path => '/File/_Quit', |
| 114 |
|
|
accelerator => '<control>Q', |
| 115 |
lvictor |
3504 |
callback => \&CloseAppWindow }, |
| 116 |
|
|
|
| 117 |
lvictor |
3505 |
{ path => '/R_eader', |
| 118 |
lvictor |
3504 |
type => '<Branch>' }, |
| 119 |
|
|
{ path => '/Reader/_Connect', |
| 120 |
|
|
accelerator => '<control>C', |
| 121 |
|
|
callback => \&ConnectDefaultReader }, |
| 122 |
|
|
{ path => '/Reader/Reco_nnect', |
| 123 |
lvictor |
3509 |
accelerator => '<control>R', |
| 124 |
lvictor |
3504 |
callback => \&ReconnectDefaultReader }, |
| 125 |
|
|
{ path => '/Reader/_Disconnect', |
| 126 |
|
|
accelerator => '<control>D', |
| 127 |
|
|
callback => \&DisconnectDefaultReader }, |
| 128 |
|
|
{ path => '/Reader/sep', |
| 129 |
|
|
type => '<Separator>' }, |
| 130 |
lvictor |
3505 |
{ path => '/Reader/_Status...', |
| 131 |
|
|
callback => \&DefaultReaderStatus }, |
| 132 |
|
|
|
| 133 |
lvictor |
3509 |
{ path => '/Ru_n', |
| 134 |
lvictor |
3505 |
type => '<Branch>' }, |
| 135 |
|
|
{ path => '/Run/_Run Script', |
| 136 |
lvictor |
3509 |
accelerator => '<control>n', |
| 137 |
lvictor |
3504 |
callback => \&RunScript }, |
| 138 |
lvictor |
3505 |
{ path => '/Run/C_lear Result Area', |
| 139 |
lvictor |
3504 |
accelerator => '<control>L', |
| 140 |
|
|
callback => \&ClearResult }, |
| 141 |
|
|
|
| 142 |
|
|
{ path => '/_Settings', |
| 143 |
|
|
type => '<Branch>' }, |
| 144 |
|
|
{ path => '/Settings/Reader', |
| 145 |
|
|
callback => \&ReaderConfig }, |
| 146 |
|
|
|
| 147 |
|
|
{ path => '/_Help', |
| 148 |
|
|
type => '<LastBranch>' }, |
| 149 |
|
|
{ path => '/_Help/About', |
| 150 |
|
|
callback => \&Help }); |
| 151 |
|
|
|
| 152 |
|
|
my $accel_group = new Gtk::AccelGroup(); |
| 153 |
|
|
my $item_factory = new Gtk::ItemFactory( 'Gtk::MenuBar', '<main>', $accel_group); |
| 154 |
|
|
$item_factory->create_items( @menu_items ); |
| 155 |
|
|
$wndMain->add_accel_group( $accel_group ); |
| 156 |
|
|
|
| 157 |
|
|
# create and arrange box containers |
| 158 |
|
|
my $vbxMainBox = new Gtk::VBox(0,3); |
| 159 |
rousseau |
3658 |
my $hbxScriptBox = new Gtk::HBox(0,3); |
| 160 |
|
|
my $vbxScriptBox = new Gtk::VBox(0,10); |
| 161 |
|
|
my $hbxResultBox = new Gtk::HBox(0,3); |
| 162 |
|
|
my $vbxResultBox = new Gtk::VBox(0,3); |
| 163 |
lvictor |
3504 |
|
| 164 |
|
|
$hbxScriptBox->add($txtScript); |
| 165 |
|
|
$hbxScriptBox->pack_end($vscScript, 0, 0, 0); |
| 166 |
|
|
$vbxScriptBox->add($hbxScriptBox); |
| 167 |
|
|
$vbxScriptBox->pack_end($btnRun, 0, 0, 0); |
| 168 |
|
|
$vbxScriptBox->pack_end($chkWrap, 0, 0, 0); |
| 169 |
|
|
$vbxScriptBox->set_border_width(5); |
| 170 |
|
|
$hbxResultBox->add($txtResult); |
| 171 |
|
|
$hbxResultBox->pack_end($vscResult, 0, 0, 0); |
| 172 |
|
|
$vbxResultBox->add($hbxResultBox); |
| 173 |
|
|
$vbxResultBox->pack_start($rdbASCII, 0, 0, 0); |
| 174 |
|
|
$vbxResultBox->pack_start($rdbHex, 0, 0, 0); |
| 175 |
|
|
$vbxResultBox->set_border_width(5); |
| 176 |
|
|
|
| 177 |
|
|
|
| 178 |
|
|
# create and fill frame containers |
| 179 |
|
|
my $frmScript = new Gtk::Frame ("Script"); |
| 180 |
|
|
my $frmResult = new Gtk::Frame ("Result"); |
| 181 |
|
|
$frmScript->set_border_width(5); |
| 182 |
|
|
$frmScript->add ($vbxScriptBox); |
| 183 |
|
|
$frmResult->set_border_width(5); |
| 184 |
|
|
$frmResult->add ($vbxResultBox); |
| 185 |
|
|
|
| 186 |
rousseau |
3658 |
my $hpaned = new Gtk::HPaned; |
| 187 |
|
|
$hpaned->border_width(5); |
| 188 |
lvictor |
3504 |
|
| 189 |
|
|
my $hbxBox = new Gtk::HBox (0,10); |
| 190 |
rousseau |
3658 |
$hpaned->add1($frmScript); |
| 191 |
|
|
$hpaned->add2($frmResult); |
| 192 |
|
|
$hbxBox->add($hpaned); |
| 193 |
lvictor |
3504 |
$vbxMainBox->pack_start ($item_factory->get_widget( '<main>' ), 0, 0, 0); |
| 194 |
|
|
$vbxMainBox->add ($hbxBox); |
| 195 |
|
|
$vbxMainBox->pack_end ($txtStatus, 0, 0, 0); |
| 196 |
|
|
$vbxMainBox->show_all(); |
| 197 |
|
|
$wndMain->add($vbxMainBox); |
| 198 |
|
|
} |
| 199 |
|
|
|
| 200 |
rousseau |
3658 |
# Connect widgets together |
| 201 |
lvictor |
3504 |
$chkWrap->signal_connect('toggled', sub { $txtScript->set_line_wrap($chkWrap->active); } ); |
| 202 |
|
|
$wndMain->signal_connect ("delete_event", \&CloseAppWindow); |
| 203 |
|
|
$btnRun->signal_connect ("clicked", \&RunScript); |
| 204 |
|
|
$rdbASCII->signal_connect('toggled', \&RefreshResult); |
| 205 |
|
|
$rdbHex->signal_connect('toggled', \&RefreshResult); |
| 206 |
|
|
|
| 207 |
|
|
ReadConfigFile(); |
| 208 |
|
|
arrange_widgets(); |
| 209 |
|
|
|
| 210 |
|
|
############################### create widgets ############################### |
| 211 |
|
|
|
| 212 |
|
|
$txtStatus->set_text ("welcome to the $strAppName application !!! - not connected"); |
| 213 |
|
|
|
| 214 |
|
|
# Show up everything as we should be ready by now |
| 215 |
|
|
$wndMain->show_all(); |
| 216 |
|
|
|
| 217 |
|
|
if (exists $hConfig{'script'}) { |
| 218 |
|
|
unless (ReadScriptFile ($hConfig{'script'})) { |
| 219 |
lvictor |
3507 |
MessageBox ("Can not open $hConfig{'script'}: $!", ['OK']); |
| 220 |
lvictor |
3504 |
delete $hConfig{'script'}; |
| 221 |
|
|
} |
| 222 |
|
|
} |
| 223 |
|
|
|
| 224 |
|
|
|
| 225 |
|
|
main Gtk; |
| 226 |
|
|
die ("The Gtk event loop failed to start resulting in abnormal program termination...\n"); |
| 227 |
|
|
|
| 228 |
|
|
######################### application engine follows ######################### |
| 229 |
|
|
|
| 230 |
|
|
sub MessageBox { |
| 231 |
|
|
my $strMessage = shift; |
| 232 |
lvictor |
3512 |
my $strTitle = shift unless ref ($_[0]); |
| 233 |
lvictor |
3504 |
my @ButtonDescr = @_; |
| 234 |
|
|
|
| 235 |
|
|
my $refCurButtonDescr; |
| 236 |
rousseau |
3514 |
my $last_button; |
| 237 |
lvictor |
3504 |
|
| 238 |
|
|
# Choose a nice title to our message box |
| 239 |
|
|
$strTitle = $strAppName unless $strTitle; |
| 240 |
rousseau |
3515 |
@ButtonDescr = ["OK"] unless @ButtonDescr; |
| 241 |
lvictor |
3504 |
|
| 242 |
|
|
my $dlgDialog = new Gtk::Dialog (); |
| 243 |
|
|
my $hbxButtonBox = new Gtk::HBox (0,3); |
| 244 |
|
|
my $hbxTextBox = new Gtk::HBox (0,3); |
| 245 |
|
|
|
| 246 |
|
|
# build up all the buttons and associate callbacks |
| 247 |
|
|
foreach $refCurButtonDescr (reverse @ButtonDescr) { |
| 248 |
|
|
croak ("invalid button name") unless scalar ($$refCurButtonDescr[0]); |
| 249 |
|
|
$$refCurButtonDescr[1] = sub { $dlgDialog->destroy(); } if $#$refCurButtonDescr == 0; |
| 250 |
lvictor |
3507 |
warn ("button named '$$refCurButtonDescr[0]' has an invalid callback: '$$refCurButtonDescr[1]'") |
| 251 |
lvictor |
3504 |
unless ref ($$refCurButtonDescr[1]); |
| 252 |
|
|
|
| 253 |
lvictor |
3512 |
# Each button is constructed from its label |
| 254 |
lvictor |
3504 |
my $btnTmpButton = new Gtk::Button($$refCurButtonDescr[0]); |
| 255 |
lvictor |
3523 |
$btnTmpButton->can_default(1); |
| 256 |
lvictor |
3512 |
|
| 257 |
|
|
# A mouse click on the button triggers a call to its embedded |
| 258 |
|
|
# callback then destroys the dialog box |
| 259 |
lvictor |
3504 |
$btnTmpButton->signal_connect ("clicked", sub { &{$$refCurButtonDescr[1]}; $dlgDialog->destroy(); }); |
| 260 |
|
|
$hbxButtonBox->pack_end($btnTmpButton, 0, 0, 10); |
| 261 |
rousseau |
3514 |
|
| 262 |
|
|
$last_button = $btnTmpButton; |
| 263 |
lvictor |
3504 |
} |
| 264 |
|
|
|
| 265 |
|
|
$dlgDialog->action_area->pack_start($hbxButtonBox, 1, 1, 0 ); |
| 266 |
|
|
my $label = new Gtk::Label($strMessage); |
| 267 |
|
|
$hbxTextBox->pack_start($label, 0, 0, 30); |
| 268 |
|
|
$dlgDialog->vbox->add ($hbxTextBox); |
| 269 |
|
|
|
| 270 |
rousseau |
3514 |
# the last button is the default one |
| 271 |
|
|
$last_button->grab_default(); |
| 272 |
|
|
|
| 273 |
lvictor |
3504 |
# Finally show up everything |
| 274 |
|
|
$dlgDialog->set_policy (0, 0, 1); |
| 275 |
|
|
$dlgDialog->set_default_size (0,0); |
| 276 |
|
|
$dlgDialog->set_title ($strTitle); |
| 277 |
|
|
$dlgDialog->set_modal(1); |
| 278 |
|
|
$dlgDialog->show_all(); |
| 279 |
|
|
} |
| 280 |
|
|
|
| 281 |
|
|
sub ReaderConfig { |
| 282 |
|
|
my $dlgReaderConfig = new Gtk::Dialog (); |
| 283 |
|
|
my $txtReader = new Gtk::Label ("Reader"); |
| 284 |
|
|
my $cboReaders = new Gtk::Combo(); |
| 285 |
|
|
my $rdbT0 = new Gtk::RadioButton("T=0"); |
| 286 |
|
|
my $rdbT1 = new Gtk::RadioButton("T=1", $rdbT0); |
| 287 |
|
|
my $rdbRAW = new Gtk::RadioButton("T=RAW", $rdbT0); |
| 288 |
lvictor |
3513 |
my $txtProtocol = new Gtk::Label ("Protocol"); |
| 289 |
lvictor |
3504 |
|
| 290 |
|
|
# This sub adjusts the content of $hConfig with the selected protocol |
| 291 |
|
|
my $subProtocol = sub { |
| 292 |
rousseau |
3626 |
$hConfig{'protocol'} = $Chipcard::PCSC::SCARD_PROTOCOL_RAW if ($rdbRAW->active); |
| 293 |
|
|
$hConfig{'protocol'} = $Chipcard::PCSC::SCARD_PROTOCOL_T0 if ($rdbT0->active); |
| 294 |
|
|
$hConfig{'protocol'} = $Chipcard::PCSC::SCARD_PROTOCOL_T1 if ($rdbT1->active); |
| 295 |
lvictor |
3504 |
}; |
| 296 |
|
|
$rdbT0->signal_connect ('toggled', $subProtocol); |
| 297 |
|
|
$rdbT1->signal_connect ('toggled', $subProtocol); |
| 298 |
|
|
$rdbRAW->signal_connect('toggled', $subProtocol); |
| 299 |
|
|
|
| 300 |
|
|
my $btnOK = new Gtk::Button ("OK"); |
| 301 |
|
|
$btnOK->signal_connect( "clicked", sub { |
| 302 |
|
|
$hConfig{'reader'} = $cboReaders->entry->get_text(); |
| 303 |
rousseau |
3626 |
$hCard->Disconnect($Chipcard::PCSC::SCARD_UNPOWER_CARD); |
| 304 |
lvictor |
3504 |
$dlgReaderConfig->destroy(); |
| 305 |
|
|
}); |
| 306 |
lvictor |
3523 |
$btnOK->can_default(1); |
| 307 |
lvictor |
3504 |
|
| 308 |
|
|
my $btnCancel = new Gtk::Button ("Cancel"); |
| 309 |
|
|
$btnCancel->signal_connect( "clicked", sub { |
| 310 |
|
|
$dlgReaderConfig->destroy(); |
| 311 |
|
|
}); |
| 312 |
lvictor |
3523 |
$btnCancel->can_default(1); |
| 313 |
lvictor |
3504 |
|
| 314 |
|
|
my $hbxReaderBox = new Gtk::HBox (0,10); |
| 315 |
|
|
my $hbxButtonBox = new Gtk::HBox (0,3); |
| 316 |
rousseau |
3516 |
my $hbxStatusBox = new Gtk::Table (3,2,1); |
| 317 |
lvictor |
3504 |
|
| 318 |
|
|
$hbxReaderBox->pack_start ($txtReader, 0, 0, 10); |
| 319 |
|
|
$hbxReaderBox->pack_start ($cboReaders, 1, 1, 10); |
| 320 |
|
|
|
| 321 |
|
|
$hbxButtonBox->pack_end($btnCancel, 0, 0, 10); |
| 322 |
|
|
$hbxButtonBox->pack_end($btnOK, 0, 0, 10); |
| 323 |
|
|
|
| 324 |
lvictor |
3512 |
# Select the last prefered protocol if any |
| 325 |
|
|
if (defined $hConfig {'protocol'}) { |
| 326 |
rousseau |
3626 |
$rdbT0->set_active(1) if ($hConfig {'protocol'} == $Chipcard::PCSC::SCARD_PROTOCOL_T0); |
| 327 |
|
|
$rdbT1->set_active(1) if ($hConfig {'protocol'} == $Chipcard::PCSC::SCARD_PROTOCOL_T1); |
| 328 |
|
|
$rdbRAW->set_active(1) if ($hConfig {'protocol'} == $Chipcard::PCSC::SCARD_PROTOCOL_RAW); |
| 329 |
lvictor |
3512 |
} |
| 330 |
lvictor |
3504 |
|
| 331 |
lvictor |
3513 |
$hbxStatusBox->attach_defaults ($txtProtocol, 0, 1, 0, 1); |
| 332 |
|
|
$hbxStatusBox->attach_defaults ($rdbT0, 1, 2, 0, 1); |
| 333 |
|
|
$hbxStatusBox->attach_defaults ($rdbT1, 1, 2, 1, 2); |
| 334 |
|
|
$hbxStatusBox->attach_defaults ($rdbRAW, 1, 2, 2, 3); |
| 335 |
lvictor |
3504 |
|
| 336 |
|
|
$cboReaders->set_popdown_strings ($hContext->ListReaders ()); |
| 337 |
|
|
$cboReaders->set_case_sensitive(1); |
| 338 |
|
|
$cboReaders->set_value_in_list(1, 0); |
| 339 |
|
|
# We select the last reader if available |
| 340 |
|
|
if (exists $hConfig{'reader'}) { |
| 341 |
|
|
$cboReaders->entry->set_text($hConfig{'reader'}); |
| 342 |
|
|
} |
| 343 |
|
|
|
| 344 |
|
|
$dlgReaderConfig->action_area->pack_start($hbxButtonBox, 1, 1, 0 ); |
| 345 |
|
|
$dlgReaderConfig->vbox->add ($hbxReaderBox); |
| 346 |
|
|
$dlgReaderConfig->vbox->add (new Gtk::HSeparator()); |
| 347 |
|
|
$dlgReaderConfig->vbox->add ($hbxStatusBox); |
| 348 |
|
|
|
| 349 |
rousseau |
3514 |
# the OK button is the default one |
| 350 |
|
|
$btnOK->grab_default(); |
| 351 |
|
|
|
| 352 |
lvictor |
3504 |
$dlgReaderConfig->set_policy (0, 0, 1); |
| 353 |
|
|
$dlgReaderConfig->set_default_size (0,0); |
| 354 |
|
|
$dlgReaderConfig->set_modal(1); |
| 355 |
|
|
$dlgReaderConfig->show_all(); |
| 356 |
|
|
} |
| 357 |
|
|
|
| 358 |
|
|
sub ReadConfigFile { |
| 359 |
|
|
if (-f $strConfigFileName) { |
| 360 |
lvictor |
3507 |
die ("Can't open $strConfigFileName for reading: $!\n") unless (open (FILE, "<$strConfigFileName")); |
| 361 |
lvictor |
3504 |
while (<FILE>) { |
| 362 |
|
|
chomp; |
| 363 |
|
|
next if /^#/; |
| 364 |
|
|
next if /^\s*$/; |
| 365 |
lvictor |
3505 |
if (/^\s*(\S+)\s*=\s*\'(.*)\'$/) { |
| 366 |
lvictor |
3504 |
$hConfig{$1} = $2; |
| 367 |
|
|
} else { |
| 368 |
|
|
print STDERR "Error while parsing $strConfigFileName\n\t'$_'\n"; |
| 369 |
|
|
} |
| 370 |
|
|
} |
| 371 |
|
|
close (FILE); |
| 372 |
|
|
} else { |
| 373 |
|
|
print STDERR "Couldn't read from $strConfigFileName. Using default configuration\n"; |
| 374 |
|
|
} |
| 375 |
|
|
} |
| 376 |
|
|
|
| 377 |
|
|
sub WriteConfigFile { |
| 378 |
|
|
my $strTmpKey; |
| 379 |
|
|
|
| 380 |
lvictor |
3507 |
die ("Can't open $strConfigFileName for writing: $!\n") unless (open (FILE, ">$strConfigFileName")); |
| 381 |
lvictor |
3504 |
print FILE "# This file is automatically generated\n# Do not edit unless you know what you are doing\n\n"; |
| 382 |
|
|
foreach $strTmpKey (keys %hConfig) { |
| 383 |
lvictor |
3505 |
print FILE "$strTmpKey = \'$hConfig{$strTmpKey}\'\n"; |
| 384 |
lvictor |
3504 |
} |
| 385 |
|
|
close (FILE); |
| 386 |
|
|
} |
| 387 |
|
|
|
| 388 |
|
|
sub ReadScriptFile { |
| 389 |
|
|
my $strScriptFileName = shift; |
| 390 |
|
|
my $strBaseFileName = basename ($strScriptFileName); |
| 391 |
|
|
|
| 392 |
|
|
return 0 unless open (FILE, "<$strScriptFileName"); |
| 393 |
|
|
$txtScript->freeze(); |
| 394 |
|
|
$txtScript->delete_text (0,-1); |
| 395 |
rousseau |
3517 |
while (<FILE>) |
| 396 |
|
|
{ |
| 397 |
rousseau |
3518 |
$txtScript->insert_text ($_, $txtScript->get_length); |
| 398 |
rousseau |
3517 |
} |
| 399 |
lvictor |
3504 |
$txtScript->thaw(); |
| 400 |
|
|
close (FILE); |
| 401 |
|
|
# Upon succesfull completion, we also set the window title as well |
| 402 |
|
|
# as the current ScriptfileName in the configuration hash |
| 403 |
|
|
$hConfig{'script'} = $strScriptFileName; |
| 404 |
|
|
$wndMain->set_title ("$strAppName - <$strBaseFileName>"); |
| 405 |
|
|
return 1; |
| 406 |
|
|
} |
| 407 |
|
|
|
| 408 |
|
|
sub WriteScriptFile { |
| 409 |
|
|
my $strScriptFileName = shift; |
| 410 |
|
|
my $strBaseFileName = basename ($strScriptFileName); |
| 411 |
|
|
|
| 412 |
|
|
return 0 unless open (FILE, ">$strScriptFileName"); |
| 413 |
|
|
print FILE $txtScript->get_chars (0,-1); |
| 414 |
|
|
close (FILE); |
| 415 |
|
|
# Upon successfull completion, we also set the window title as well |
| 416 |
|
|
# as the current ScriptfileName in the configuration hash |
| 417 |
|
|
$hConfig{'script'} = $strScriptFileName; |
| 418 |
|
|
$wndMain->set_title ("$strAppName - <$strBaseFileName>"); |
| 419 |
|
|
return 1; |
| 420 |
|
|
} |
| 421 |
|
|
|
| 422 |
|
|
sub NewScriptFile { |
| 423 |
|
|
if ($txtScript->get_length()) { |
| 424 |
|
|
# That call to MessageBox will connect the OK button with a callback |
| 425 |
|
|
# that actually erase the script |
| 426 |
|
|
MessageBox( |
| 427 |
|
|
"The current work will be lost !\nAre you sure you want to continue ?", |
| 428 |
|
|
["OK", sub { EraseCurrentScript(); } ], |
| 429 |
|
|
["CANCEL"] |
| 430 |
|
|
); |
| 431 |
|
|
} |
| 432 |
|
|
} |
| 433 |
|
|
|
| 434 |
|
|
sub EraseCurrentScript { |
| 435 |
|
|
$txtScript->delete_text (0,-1); |
| 436 |
|
|
$wndMain->set_title ("$strAppName"); |
| 437 |
|
|
delete $hConfig{'script'}; |
| 438 |
|
|
} |
| 439 |
|
|
|
| 440 |
|
|
sub SaveScriptFile { |
| 441 |
|
|
my ($widget) = @_; |
| 442 |
|
|
if (exists $hConfig{'script'}) { |
| 443 |
|
|
WriteScriptFile ($hConfig{'script'}); |
| 444 |
|
|
} else { |
| 445 |
|
|
SaveAsScriptFile ($widget); |
| 446 |
|
|
} |
| 447 |
|
|
} |
| 448 |
|
|
|
| 449 |
|
|
sub SaveAsScriptFile { |
| 450 |
|
|
my ($widget) = @_; |
| 451 |
|
|
|
| 452 |
|
|
my $file_dialog = new Gtk::FileSelection( "Save File" ); |
| 453 |
|
|
$file_dialog->ok_button->signal_connect( "clicked", sub { |
| 454 |
|
|
my $file = $file_dialog->get_filename(); |
| 455 |
|
|
|
| 456 |
|
|
if (-d $file) { |
| 457 |
lvictor |
3507 |
MessageBox ("Can't open $file: file is a directory", ['OK']); |
| 458 |
lvictor |
3504 |
} else { |
| 459 |
lvictor |
3507 |
MessageBox ("Can't open $file: $!", ['OK']) unless WriteScriptFile ($file); |
| 460 |
lvictor |
3504 |
} |
| 461 |
|
|
$file_dialog->destroy(); |
| 462 |
|
|
}); |
| 463 |
|
|
$file_dialog->cancel_button->signal_connect( "clicked", sub { $file_dialog->destroy(); }); |
| 464 |
|
|
if (exists $hConfig{'script'}) { |
| 465 |
|
|
$file_dialog->set_filename(dirname($hConfig{'script'})) |
| 466 |
|
|
} |
| 467 |
|
|
|
| 468 |
|
|
$file_dialog->show(); |
| 469 |
|
|
} |
| 470 |
|
|
|
| 471 |
|
|
sub LoadScriptFile { |
| 472 |
|
|
if ($txtScript->get_length()) { |
| 473 |
|
|
MessageBox( |
| 474 |
|
|
"The current work will be lost !\nAre you sure you want to continue ?", |
| 475 |
|
|
["OK", \&LoadScript], |
| 476 |
|
|
["CANCEL"] |
| 477 |
|
|
); |
| 478 |
|
|
} else { |
| 479 |
|
|
LoadScript(); |
| 480 |
|
|
} |
| 481 |
|
|
} |
| 482 |
|
|
|
| 483 |
|
|
sub LoadScript { |
| 484 |
|
|
my $file_dialog = new Gtk::FileSelection( "Load File" ); |
| 485 |
|
|
$file_dialog->ok_button->signal_connect( "clicked", sub { |
| 486 |
|
|
my $file = $file_dialog->get_filename(); |
| 487 |
|
|
|
| 488 |
|
|
if (-d $file) { |
| 489 |
lvictor |
3507 |
MessageBox ("Can't open $file: file is a directory", ['OK']); |
| 490 |
lvictor |
3504 |
} else { |
| 491 |
lvictor |
3507 |
MessageBox ("Can't open $file: $!", ['OK']) unless ReadScriptFile ($file); |
| 492 |
lvictor |
3504 |
} |
| 493 |
|
|
$file_dialog->destroy(); |
| 494 |
|
|
}); |
| 495 |
|
|
$file_dialog->cancel_button->signal_connect( "clicked", sub { $file_dialog->destroy(); }); |
| 496 |
|
|
if (exists $hConfig{'script'}) { |
| 497 |
lvictor |
3505 |
$file_dialog->set_filename(dirname($hConfig{'script'})."/"); |
| 498 |
lvictor |
3504 |
} |
| 499 |
|
|
|
| 500 |
|
|
$file_dialog->show(); |
| 501 |
|
|
} |
| 502 |
|
|
|
| 503 |
|
|
sub ClearResult { |
| 504 |
|
|
$txtResult->delete_text (0, -1); |
| 505 |
|
|
@ResultStruct = (); |
| 506 |
|
|
} |
| 507 |
|
|
|
| 508 |
|
|
sub RefreshResult { |
| 509 |
|
|
my $tmpLine; |
| 510 |
|
|
my $tmp_value; |
| 511 |
|
|
|
| 512 |
|
|
$txtResult->freeze(); |
| 513 |
|
|
$txtResult->delete_text (0, -1); |
| 514 |
|
|
foreach $tmpLine (@ResultStruct) { |
| 515 |
|
|
if (ref $tmpLine) { |
| 516 |
|
|
foreach $tmp_value (@$tmpLine) { |
| 517 |
|
|
if ($rdbHex->active) { |
| 518 |
rousseau |
3518 |
$txtResult->insert_text (sprintf ("%02X ", $tmp_value), $txtResult->get_length); |
| 519 |
lvictor |
3504 |
} else { |
| 520 |
lvictor |
3505 |
# TODO: enhance filtering of non-printable chars |
| 521 |
lvictor |
3509 |
# TODO: how can we use array_to_ascii here ??? maybe |
| 522 |
|
|
# enhence the function so it can directly receive |
| 523 |
|
|
# ranges of chars to ignore optionally ? |
| 524 |
lvictor |
3505 |
if (($tmp_value > 0x20) && ($tmp_value < 0x80)) { |
| 525 |
rousseau |
3518 |
$txtResult->insert_text (chr ($tmp_value), $txtResult->get_length); |
| 526 |
lvictor |
3505 |
} else { |
| 527 |
rousseau |
3518 |
$txtResult->insert_text ('.', $txtResult->get_length); |
| 528 |
lvictor |
3505 |
} |
| 529 |
lvictor |
3504 |
} |
| 530 |
|
|
} |
| 531 |
rousseau |
3518 |
$txtResult->insert_text ("\n", $txtResult->get_length); |
| 532 |
lvictor |
3504 |
} else { |
| 533 |
rousseau |
3518 |
$txtResult->insert_text ($tmpLine, $txtResult->get_length); |
| 534 |
lvictor |
3504 |
} |
| 535 |
|
|
} |
| 536 |
|
|
$txtResult->thaw(); |
| 537 |
|
|
} |
| 538 |
|
|
|
| 539 |
|
|
sub ConnectDefaultReader { |
| 540 |
lvictor |
3505 |
if (exists $hConfig{'reader'} && !($hConfig{'reader'} eq '')) { |
| 541 |
lvictor |
3504 |
if (defined $hCard->{hCard}) { |
| 542 |
|
|
MessageBox ( |
| 543 |
|
|
"The card is already connected...\nDo you want to reconnect ?", |
| 544 |
|
|
['OK', \&ReconnectDefaultReader], |
| 545 |
|
|
['CANCEL'] |
| 546 |
|
|
); |
| 547 |
|
|
} else { |
| 548 |
rousseau |
3626 |
$hCard->Connect ($hConfig{'reader'}, $Chipcard::PCSC::SCARD_SHARE_EXCLUSIVE, $hConfig{'protocol'}); |
| 549 |
lvictor |
3504 |
if (defined $hCard->{hCard}) { |
| 550 |
|
|
$txtStatus->set_text ("Connected to '$hConfig{'reader'}'"); |
| 551 |
|
|
} else { |
| 552 |
rousseau |
3626 |
MessageBox ("Can not connect to the reader named '$hConfig{'reader'}':\n$Chipcard::PCSC::errno", ['OK']) |
| 553 |
lvictor |
3504 |
}; |
| 554 |
|
|
} |
| 555 |
|
|
} else { |
| 556 |
|
|
MessageBox ("No default reader has been configured\nPlease make sure you have configured a reader first", ['OK']); |
| 557 |
|
|
} |
| 558 |
|
|
} |
| 559 |
|
|
|
| 560 |
|
|
sub ReconnectDefaultReader { |
| 561 |
|
|
if (defined $hCard->{hCard}) { |
| 562 |
rousseau |
3626 |
$hCard->Reconnect ($Chipcard::PCSC::SCARD_SHARE_EXCLUSIVE, $hConfig{'protocol'}, $Chipcard::PCSC::SCARD_RESET_CARD); |
| 563 |
lvictor |
3504 |
if (defined $hCard->{hCard}) { |
| 564 |
|
|
$txtStatus->set_text ("Connected to '$hConfig{'reader'}'"); |
| 565 |
|
|
} else { |
| 566 |
rousseau |
3626 |
MessageBox ("Can not reconnect to the reader named '$hConfig{'reader'}':\n$Chipcard::PCSC::errno", ['OK']) |
| 567 |
lvictor |
3504 |
}; |
| 568 |
|
|
} else { |
| 569 |
lvictor |
3505 |
# we just propose to connect but do not call Reconnect() |
| 570 |
|
|
# afterwards that would be quite useless |
| 571 |
rousseau |
3626 |
MessageBox ("The Chipcard::PCSC:Card object is not connected...\n Do you want to connect ?", |
| 572 |
lvictor |
3505 |
['OK', sub {ConnectDefaultReader();}], |
| 573 |
|
|
['CANCEL']); |
| 574 |
lvictor |
3504 |
} |
| 575 |
|
|
} |
| 576 |
|
|
|
| 577 |
|
|
sub DisconnectDefaultReader { |
| 578 |
|
|
if (defined $hCard->{hCard}) { |
| 579 |
rousseau |
3626 |
$hCard->Disconnect($Chipcard::PCSC::SCARD_UNPOWER_CARD); |
| 580 |
lvictor |
3504 |
if (defined $hCard->{hCard}) { |
| 581 |
rousseau |
3626 |
MessageBox ("Can not disconnect from the reader named '$hConfig{'reader'}':\n$Chipcard::PCSC::errno", ['OK']); |
| 582 |
lvictor |
3504 |
} else { |
| 583 |
|
|
$txtStatus->set_text ("not connected"); |
| 584 |
|
|
}; |
| 585 |
|
|
} else { |
| 586 |
rousseau |
3626 |
MessageBox ("The Chipcard::PCSC::Card object is not connected !", ['OK']); |
| 587 |
lvictor |
3504 |
} |
| 588 |
|
|
} |
| 589 |
|
|
|
| 590 |
|
|
sub DefaultReaderStatus { |
| 591 |
lvictor |
3505 |
if (defined $hCard->{hCard}) { |
| 592 |
|
|
my @StatusResult = $hCard->Status(); |
| 593 |
|
|
if (defined $StatusResult[0]) { |
| 594 |
lvictor |
3506 |
#TODO This stinks can't I rewrite it so it looks better&shorter |
| 595 |
|
|
my $tmpVal = 0; |
| 596 |
lvictor |
3505 |
|
| 597 |
lvictor |
3506 |
my $MessageString = "You are connected to reader $StatusResult[0].\n"; |
| 598 |
|
|
$MessageString .= "Card status (". sprintf ("0x%04X",$StatusResult[1]) ."): "; |
| 599 |
|
|
|
| 600 |
|
|
# Verbosely describes the Card Status (powered, inserted, etc... |
| 601 |
rousseau |
3626 |
if ($StatusResult[1] & $Chipcard::PCSC::SCARD_UNKNOWN) { |
| 602 |
lvictor |
3506 |
if ($tmpVal) { $MessageString .= ", "; } ++$tmpVal; |
| 603 |
|
|
$MessageString .= "'Unknown state'"; |
| 604 |
|
|
} |
| 605 |
rousseau |
3626 |
if ($StatusResult[1] & $Chipcard::PCSC::SCARD_ABSENT) { |
| 606 |
lvictor |
3506 |
if ($tmpVal) { $MessageString .= ", "; } ++$tmpVal; |
| 607 |
|
|
$MessageString .= "Absent"; |
| 608 |
|
|
} |
| 609 |
rousseau |
3626 |
if ($StatusResult[1] & $Chipcard::PCSC::SCARD_PRESENT) { |
| 610 |
lvictor |
3506 |
if ($tmpVal) { $MessageString .= ", "; } ++$tmpVal; |
| 611 |
|
|
$MessageString .= "Present"; |
| 612 |
|
|
} |
| 613 |
rousseau |
3626 |
if ($StatusResult[1] & $Chipcard::PCSC::SCARD_SWALLOWED) { |
| 614 |
lvictor |
3506 |
if ($tmpVal) { $MessageString .= ", "; } ++$tmpVal; |
| 615 |
|
|
$MessageString .= "Swallowed"; |
| 616 |
|
|
} |
| 617 |
rousseau |
3626 |
if ($StatusResult[1] & $Chipcard::PCSC::SCARD_POWERED) { |
| 618 |
lvictor |
3506 |
if ($tmpVal) { $MessageString .= ", "; } ++$tmpVal; |
| 619 |
|
|
$MessageString .= "Powered"; |
| 620 |
|
|
} |
| 621 |
rousseau |
3626 |
if ($StatusResult[1] & $Chipcard::PCSC::SCARD_NEGOTIABLE) { |
| 622 |
lvictor |
3506 |
if ($tmpVal) { $MessageString .= ", "; } ++$tmpVal; |
| 623 |
|
|
$MessageString .= "'PTS is negotiable'"; |
| 624 |
|
|
} |
| 625 |
rousseau |
3626 |
if ($StatusResult[1] & $Chipcard::PCSC::SCARD_SPECIFIC) { |
| 626 |
lvictor |
3506 |
if ($tmpVal) { $MessageString .= ", "; } ++$tmpVal; |
| 627 |
|
|
$MessageString .= "'PTS has been set'"; |
| 628 |
|
|
} |
| 629 |
|
|
|
| 630 |
|
|
# Verbosely describes the currently active protocol |
| 631 |
|
|
$MessageString .= ".\nThe active protocol (".sprintf ("0x%04X", $StatusResult[2]).") is "; |
| 632 |
rousseau |
3626 |
if (($StatusResult[2] & $Chipcard::PCSC::SCARD_PROTOCOL_T0)) { |
| 633 |
lvictor |
3506 |
$MessageString .= "T=0 "; |
| 634 |
rousseau |
3626 |
} elsif (($StatusResult[2] & $Chipcard::PCSC::SCARD_PROTOCOL_T1)) { |
| 635 |
lvictor |
3506 |
$MessageString .= "T=1 "; |
| 636 |
rousseau |
3626 |
} elsif (($StatusResult[2] & $Chipcard::PCSC::SCARD_PROTOCOL_RAW)) { |
| 637 |
lvictor |
3506 |
$MessageString .= "RAW "; |
| 638 |
|
|
} else { |
| 639 |
|
|
$MessageString .= "unknown"; |
| 640 |
|
|
} |
| 641 |
|
|
|
| 642 |
|
|
# Displays the ATR (if available) |
| 643 |
|
|
$MessageString .= ".\nThe ATR is "; |
| 644 |
|
|
if (defined $StatusResult[3]) { |
| 645 |
|
|
foreach $tmpVal (@{$StatusResult[3]}) { $MessageString .= sprintf ("%02X ", $tmpVal); } |
| 646 |
|
|
} else { |
| 647 |
|
|
$MessageString .= "not available"; |
| 648 |
|
|
} |
| 649 |
lvictor |
3505 |
MessageBox ($MessageString, ['OK']); |
| 650 |
|
|
} else { |
| 651 |
rousseau |
3626 |
MessageBox ("Can not retrieve reader status:\n$Chipcard::PCSC::errno", ['OK']) |
| 652 |
lvictor |
3505 |
} |
| 653 |
|
|
} else { |
| 654 |
|
|
MessageBox ("The reader is not connected...\n Do you want to connect ?", |
| 655 |
|
|
['OK', sub {ConnectDefaultReader(); if (defined $hCard->{hCard}) {DefaultReaderStatus();}}], |
| 656 |
|
|
['CANCEL']); |
| 657 |
|
|
} |
| 658 |
lvictor |
3504 |
} |
| 659 |
|
|
|
| 660 |
|
|
sub RunScript { |
| 661 |
lvictor |
3505 |
if (defined $hCard->{hCard}) { |
| 662 |
lvictor |
3504 |
my @tmpCommandArray = split /\n/, $txtScript->get_chars (0,-1); |
| 663 |
|
|
my $raCurrentResult; |
| 664 |
|
|
my $nIndex; |
| 665 |
|
|
|
| 666 |
|
|
push @ResultStruct, "Beginning script execution...\n\n"; |
| 667 |
|
|
foreach $_ (@tmpCommandArray) { |
| 668 |
|
|
# this variable has to be inside the loop as we store a |
| 669 |
|
|
# reference to it in the result struct... it has to be |
| 670 |
|
|
# unique for each command line |
| 671 |
lvictor |
3509 |
my $raCurrentCommand; |
| 672 |
lvictor |
3504 |
|
| 673 |
|
|
# Skip blank lines and comments |
| 674 |
|
|
next if /^\s*$/; |
| 675 |
|
|
next if /^#/; |
| 676 |
|
|
|
| 677 |
rousseau |
3559 |
if (/reset/i) { |
| 678 |
lvictor |
3509 |
push @ResultStruct, "[Reset]\n\n"; |
| 679 |
|
|
ReconnectDefaultReader(); |
| 680 |
|
|
next; |
| 681 |
|
|
} |
| 682 |
|
|
|
| 683 |
lvictor |
3504 |
# Extract bytes from the ascii string |
| 684 |
rousseau |
3626 |
$raCurrentCommand = Chipcard::PCSC::ascii_to_array($_); |
| 685 |
lvictor |
3504 |
|
| 686 |
lvictor |
3509 |
# push them in the Display structure |
| 687 |
lvictor |
3507 |
push @ResultStruct, "Sending: "; |
| 688 |
lvictor |
3509 |
push @ResultStruct, $raCurrentCommand; |
| 689 |
lvictor |
3504 |
|
| 690 |
lvictor |
3509 |
# Transmit them to the card |
| 691 |
|
|
$raCurrentResult = $hCard->Transmit ($raCurrentCommand); |
| 692 |
lvictor |
3504 |
if (ref $raCurrentResult) { |
| 693 |
lvictor |
3507 |
push @ResultStruct, "Received: "; |
| 694 |
lvictor |
3504 |
push @ResultStruct, $raCurrentResult; |
| 695 |
rousseau |
3659 |
push @ResultStruct, Chipcard::PCSC::Card::ISO7816Error(Chipcard::PCSC::array_to_ascii($raCurrentResult)), "\n\n"; |
| 696 |
lvictor |
3504 |
} else { |
| 697 |
rousseau |
3626 |
MessageBox ("Transmit failed: $Chipcard::PCSC::errno\nStopping script execution", ['OK']); |
| 698 |
|
|
push @ResultStruct, "\nErrors During Script Execution: $Chipcard::PCSC::errno\n"; |
| 699 |
lvictor |
3504 |
last; |
| 700 |
|
|
} |
| 701 |
|
|
} |
| 702 |
|
|
push @ResultStruct, "Script was executed without error...\n"; |
| 703 |
|
|
} else { |
| 704 |
rousseau |
3626 |
MessageBox ("The Chipcard::PCSC::Card object is not connected !\nDo you want to connect ?", |
| 705 |
lvictor |
3505 |
['OK', sub {ConnectDefaultReader(); if (defined $hCard->{hCard}) {RunScript();}}], |
| 706 |
|
|
['CANCEL']); |
| 707 |
lvictor |
3504 |
} |
| 708 |
|
|
RefreshResult(); |
| 709 |
|
|
} |
| 710 |
|
|
|
| 711 |
|
|
sub CloseAppWindow { |
| 712 |
|
|
undef $hCard; |
| 713 |
|
|
undef $hContext; |
| 714 |
|
|
WriteConfigFile(); |
| 715 |
|
|
Gtk->exit(0); |
| 716 |
|
|
} |
| 717 |
|
|
|
| 718 |
|
|
sub Help { |
| 719 |
|
|
MessageBox (" |
| 720 |
rousseau |
3520 |
$strAppName is coded by Lionel VICTOR, (C) 2001. |
| 721 |
rousseau |
3628 |
and debugged by Ludovic ROUSSEAU, (C) 2001-2003. |
| 722 |
lvictor |
3504 |
|
| 723 |
rousseau |
3520 |
$strAppName is a small application written in Perl. |
| 724 |
lvictor |
3504 |
It basically demonstrates how easy it is to develop a |
| 725 |
rousseau |
3520 |
quick working prototype that uses smartcards in Perl. |
| 726 |
lvictor |
3504 |
", |
| 727 |
|
|
['OK'] |
| 728 |
|
|
); |
| 729 |
|
|
} |
| 730 |
|
|
|
| 731 |
|
|
# End of File # |
| 732 |
rousseau |
3510 |
|