1fef23f128ebb44d96636611c6fc19a6042d958c
[spider.git] / gtkconsole / gtkconsole
1 #!/usr/bin/perl -w
2 #
3 # A GTK based console program
4 #
5 # usage: gtkconsole [<callsign>] [<host> <port>]
6
7 # Copyright (c) 2001-6 Dirk Koopman G1TLH
8 #
9 # $Id$
10 #
11
12 use strict;
13
14 our $VERSION = '$Revision$';
15
16 our $root;
17
18 # search local then perl directories
19 BEGIN {
20         # root of directory tree for this system
21         $root = "/spider"; 
22         $root = $ENV{'DXSPIDER_ROOT'} if $ENV{'DXSPIDER_ROOT'};
23 }
24
25 use Glib;
26 use Gtk2 qw(-init);
27 use Gtk2::Helper;
28 use Gtk2::SimpleMenu;
29 use Data::Dumper;
30 use IO::File;
31
32 use Screen;
33
34 use vars qw(@modules $font);                    
35
36 @modules = ();                                  # is the list of modules that need init calling
37                                                                 # on them. It is set up by each  'use'ed module
38                                                                 # that has Gtk stuff in it
39 use IO::Socket::INET;
40
41 our @month = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
42
43 # various GTK handles
44 our $main;                                              # the main screen
45 our $scr_width;                                 # calculated screen dimensions
46 our $scr_height;
47 our ($dx, $cmd, $ann, $wcy, $wwv); # scrolling list windows
48 our $bot;                                               # the cmd entry window
49 our $date;                                              # the current date
50
51 # read in the user data
52 our $userfn = "$ENV{HOME}/.gtkconsole_data";
53 our $user = read_user_data();
54 our $call;
55 our $passwd;
56 our $host;
57 our $port = 7300;
58
59
60 # read in gtkconsole file
61 #
62
63 Gtk2::Rc->set_default_files("$root/gtkconsole/gtkconsolerc", "$ENV{HOME}/.gtkconsolerc", ".gtkconsolerc");
64 Gtk2::Rc->reparse_all;
65  
66 # sort out a callsign, host and port, looking in order
67 #  1. the command line
68 #  2. any defaults in the user data;
69 #  3. poke about in any spider tree that we can find
70 #
71
72 if (@ARGV) {
73         $call = uc shift @ARGV;
74         $host = shift @ARGV if @ARGV;
75         $port = shift @ARGV if @ARGV;
76 }
77
78 unless ($call && $host) {
79         my $node = $user->{clusters}->{$user->{node}};
80         
81         if ($node->{call} || $user->{call}) {
82                 $call = $node->{call} || $user->{call};
83                 $host = $node->{passwd};
84                 $host = $node->{host};
85                 $port = $node->{port};
86         }
87 }
88
89 unless ($call && $host) {
90         if (-e "$root/local/DXVars.pm") {
91                 require "$root/local/DXVars.pm";
92                 $call = $main::myalias;
93                 $call = $main::myalias; # for the warning
94         }
95         if (-e "$root/local/Listeners.pm") {
96                 require  "$root/local/Listeners.pm";
97                 $host = $main::listen->[0]->[0];
98                 $port = $main::listen->[0]->[1];
99         }
100 }
101
102 unless ($host) {
103         $host = $user->{clusters}->{$user->{node}}->{host};
104         $port = $user->{clusters}->{$user->{node}}->{port};
105 }
106
107 $call ||= '';
108 $host ||= '';
109 $port ||= '';
110 die "You need a callsign ($call), a hostname($host) and a port($port) to proceed" unless $call && $host;
111
112 #
113 # start of GTK stuff
114 #
115
116 gtk_create_main_screen();
117
118 $main->set_title("gtkconsole $VERSION - DXSpider Console - $call \@ $host:$port");
119
120 # connect and send stuff
121 my $sock = IO::Socket::INET->new(PeerAddr=>$host, PeerPort=>$port);
122 die "Cannot connect to  $/$port ($!)\n" unless $sock;
123 sendmsg($call);
124 sendmsg($passwd) if $passwd;
125 sendmsg('set/gtk');
126 sendmsg('set/page 500');
127 sendmsg('set/nobeep');
128
129 my $sock_helper = Gtk2::Helper->add_watch($sock->fileno, 'in', \&tophandler, $sock);
130  
131 # the main loop
132 $main->show_all;
133 $bot->grab_focus;
134 Gtk2->main;
135 exit(0);
136
137 #
138 # handlers
139 #
140
141 sub updatetime
142 {
143         $_[0]->set_text(cldatetime(time));
144         1;
145 }
146
147 sub bothandler
148 {
149         my ($self, $data) = @_;
150         my $msg = $self->get_text;
151         $msg =~ s/\r?\n$//;
152         $self->set_text('');
153         $self->grab_focus;
154         sendmsg($msg);
155 }
156
157 my $rbuf = '';
158
159 sub tophandler
160 {
161         my ($fd, $condx, $socket) = @_;
162
163         my $offset = length $rbuf;
164         my $l = sysread($socket, $rbuf, 1024, $offset);
165         if (defined $l) {
166                 if ($l) {
167                         while ($rbuf =~ s/^([^\015\012]*)\015?\012//) {
168                                 my $msg = $1;
169                                 handlemsg($msg);
170                         }
171                 } else {
172                         Gtk2->main_quit;
173                 }
174         } else {
175                 Gtk2->main_quit;
176         }
177         1;
178         
179 }
180
181 sub handlemsg
182 {
183         my $line = shift;
184
185         # this is truely evil and I bet there is a better way...
186         chomp $line;
187         my $list;
188         if ($line =~ /^'\w{2,4}',/) {
189                 $list = eval qq([$line]);
190         } else {
191                 $list = ['cmd', $line];
192         }
193         unless ($@) {
194                 no strict 'refs';
195                 my $cmd = shift @$list;
196                 my $handle = "handle_$cmd";
197                 if (__PACKAGE__->can($handle)) {
198                         __PACKAGE__->$handle($list);
199                 } else {
200                         unshift @$list, $cmd;
201                         __PACKAGE__->handle_def($list);
202                 }
203         }
204 }
205
206 sub handle_cmd
207 {
208         my $self = shift;
209         my $ref = shift;
210         my ($t, $ts) = (time, '');
211         my $s;
212         $s = ref $ref ? join ', ',@$ref : $ref;
213
214         if (($cmd->{lasttime}||0) != $t) {
215                 $ts = tim($t);
216                 $cmd->{lasttime} = $t;
217         }
218
219         chomp $s;
220         $cmd->add_data([$ts,  $s]);
221 }
222
223 sub handle_def
224 {
225         my $self = shift;
226         my $ref = shift;
227         my ($t, $ts) = (time, '');
228         my $s;
229         $s = ref $ref ? join(', ', @$ref) : $ref;
230         if (($cmd->{lasttime}||0) != $t) {
231                 $ts = tim($t);
232                 $cmd->{lasttime} = $t;
233         }
234         $cmd->add_data([$ts,  $s]);
235 }
236
237 sub handle_dx
238 {
239         my $self = shift;
240         my $ref = shift;
241         my ($t, $ts) = (time, '');
242
243         if (($dx->{lasttime}||0) != $t) {
244                 $ts = tim($t);
245                 $dx->{lasttime} = $t;
246         }
247         $dx->add_data([$ts,  @$ref[0,1,15,3,4,16], stim($ref->[2]) ]);
248         
249 }
250
251 sub handle_ann
252 {
253         my $self = shift;
254         my $ref = shift;
255         my ($t, $ts) = (time, '');
256         my $s;
257         $s = ref $ref ? join ', ',@$ref : $ref;
258
259         if (($ann->{lasttime}||0) != $t) {
260                 $ts = tim($t);
261                 $ann->{lasttime} = $t;
262         }
263
264         chomp $s;
265         $ann->add_data([$ts,  @$ref[3,1,2]]);
266 }
267
268 sub handle_wcy
269 {
270         my $self = shift;
271         my $ref = shift;
272         my $s;
273         $s = ref $ref ? join ', ',@$ref : $ref;
274
275         chomp $s;
276
277         $wcy->add_data([tim(),  @$ref[10,4,5,3,6,2,7,8,9,1] ]);
278 }
279
280 sub handle_wwv
281 {
282         my $self = shift;
283         my $ref = shift;
284         my $s;
285         $s = ref $ref ? join ', ',@$ref : $ref;
286
287         chomp $s;
288         $wwv->add_data([tim(),  @$ref[6,2,3,4,5,1] ]);
289 }
290
291
292
293 #
294 # subroutine
295 #
296
297 sub sendmsg
298 {
299         my $msg = shift;
300         $sock->print("$msg\n");
301 }
302
303 sub tim
304 {
305         my $t = shift || time;
306         return sprintf "%02d:%02d:%02d", (gmtime($t))[2,1,0];
307 }
308
309 sub stim
310 {
311         my $t = shift || time;
312         return sprintf "%02d:%02d", (gmtime($t))[2,1];
313 }
314
315 # get a zulu time in cluster format (2300Z)
316 sub ztime
317 {
318         my $t = shift;
319         $t = defined $t ? $t : time;
320         my $dst = shift;
321         my ($sec,$min,$hour) = $dst ? localtime($t): gmtime($t);
322         my $buf = sprintf "%02d%02d%s", $hour, $min, ($dst) ? '' : 'Z';
323         return $buf;
324 }
325
326 # get a cluster format date (23-Jun-1998)
327 sub cldate
328 {
329         my $t = shift;
330         $t = defined $t ? $t : time;
331         my $dst = shift;
332         my ($sec,$min,$hour,$mday,$mon,$year) = $dst ? localtime($t) : gmtime($t);
333         $year += 1900;
334         my $buf = sprintf "%2d-%s-%04d", $mday, $month[$mon], $year;
335         return $buf;
336 }
337
338 # return a cluster style date time
339 sub cldatetime
340 {
341         my $t = shift;
342         my $dst = shift;
343         my $date = cldate($t, $dst);
344         my $time = ztime($t, $dst);
345         return "$date $time";
346 }
347
348 sub read_user_data
349 {
350         my $u;
351         
352         if (-e $userfn) {
353                 my $fh = new IO::File $userfn;
354                 my $s = undef;
355                 if ($fh) {
356                         local $/ = undef;
357                         $s = <$fh>;
358                         $fh->close;
359                 }
360                 eval "\$u = $s";
361         }
362         unless ($u) {
363                 print "$userfn missing or unreadable, starting afresh!\n";
364                 
365                 $u = {
366                           clusters => {
367                                                    'GB7DJK' => {host => 'gb7djk.dxcluster.net', port => 7300},
368                                                    'WR3D' => {host => 'wr3d.dxcluster.net', port => 7300},
369                                                    'GB7BAA' => {host => 'gb7baa.dxcluster.net', port => 7300},
370                                                   },
371                           node => 'GB7DJK',
372                          };
373                 write_user_data($u);
374         }
375         return $u;
376 }
377
378 sub write_user_data
379 {
380         my $u = shift;
381         
382         my $fh = new IO::File ">$userfn";
383         if ($fh) {
384                 my $dd = new Data::Dumper([ $u ]);
385                 $dd->Indent(1);
386                 $dd->Terse(1);
387                 $dd->Quotekeys(0);
388                 $fh->print($dd->Dumpxs);
389                 $fh->close;
390                 return 1;
391         }
392         return 0;
393 }
394
395 sub def_menu_callback
396 {
397
398 }
399
400 sub gtk_create_main_screen
401 {
402         $main = new Gtk2::Window('toplevel');
403         my $scr = $main->get_screen;
404         $scr_width = int ($scr->get_width > 1280 ? 1280 : $scr->get_width) * 0.99;
405         $scr_height = int $scr->get_height * 0.5;
406         $main->set_default_size($scr_width, $scr_height);
407         $main->signal_connect('delete_event', sub { Gtk2->main_quit; });
408
409         # the main vbox
410         my $vbox = new Gtk2::VBox(0, 1);
411         $main->add($vbox);
412
413         my $menutree = [
414                                         _File => {
415                                                           item_type => '<Branch>',
416                                                           children => [
417                                                                                    _Quit => {
418                                                                                                          callback => sub { Gtk2->main_quit; },
419                                                                                                          callback_action => 1,
420                                                                                                          accelerator => '<ctrl>Q',
421                                                                                                         }
422                                                                                   ],
423                                                          },
424
425                                         _Help => {
426                                                           item_type => '<Branch>',
427                                                           children => [
428                                                                                    _About => {
429                                                                                                           callback_action => 9,
430                                                                                                          },
431                                                                                   ],
432                                                          },
433
434                                    ];
435         
436         my $menu = Gtk2::SimpleMenu->new(menu_tree => $menutree, default_callback => \&def_menu_callback, user_data => $user);
437         $vbox->pack_start($menu->{widget}, 0, 1, 0);
438
439
440         # a paned hbox is packed as the bottom of the vbox
441         my $bhpane = Gtk2::HPaned->new;
442         $vbox->pack_end($bhpane, 1, 1, 0);
443
444         # now create the lh and rh panes
445         my $lhvpane = Gtk2::VPaned->new;
446         my $rhvpane = Gtk2::VPaned->new;
447         $bhpane->pack1($lhvpane, 1, 0);
448         $bhpane->pack2($rhvpane, 1, 0);
449
450         #
451         # LEFT HAND SIDE
452         #
453         # The announce list
454         $ann = Screen::List->new(fields =>[
455                                                                            RxTime => 'tt',
456                                                                            From => 'tt',
457                                                                            To => 'tt',
458                                                                            Announcement => 'ttlesslong',
459                                                                           ],
460                                                          hint => 1,
461                                                          frame => 'Announcements',
462                                                          size => [$scr_width * 0.45, $scr_height * 0.33],
463                                                         );
464
465         $lhvpane->pack1($ann->widget, 1, 0);
466
467         # The command list
468         my $lhvbox = Gtk2::VBox->new(0, 1);
469         $cmd = Screen::List->new(fields => [
470                                                                                 RxTime => 'tt',
471                                                                                 Information => 'ttlong',
472                                                                            ],
473                                                          size => [$scr_width * 0.45, $scr_height * 0.66],
474                                                         );
475         $lhvbox->pack_start($cmd->widget, 1, 1, 0);
476
477
478         # callsign and current date and time
479         my $hbox = new Gtk2::HBox;
480         my $calllabel = new Gtk2::Label($call);
481         my $date = new Gtk2::Label(cldatetime(time));
482         $date->{tick} = Glib::Timeout->add(1000, \&updatetime, $date);
483         $hbox->pack_start( $calllabel, 0, 1, 0 );
484         $hbox->pack_end($date, 0, 1, 0);
485         $lhvbox->pack_start($hbox, 0, 1, 0);
486         $lhvbox->pack_start(Gtk2::HSeparator->new, 0, 1, 0);
487
488         # the bottom handler
489         $bot = new Gtk2::Entry;
490         $bot->set_editable(1);
491         $bot->signal_connect('activate', \&bothandler);
492         $bot->can_default(1);
493         $lhvbox->pack_end($bot, 0, 1, 0);
494         $lhvpane->pack2($lhvbox, 1, 0);
495         $bot->grab_default;
496
497         #
498         # RIGHT HAND SIDE
499         #
500
501         # DX window
502         $dx = Screen::List->new(fields => [
503                                                                            'RxTime' => 'tt',
504                                                                            'QRG' => 'qrg',
505                                                                            'DX Call' => 'tt',
506                                                                            'Grid' => 'tt',
507                                                                            'Remarks' => 'ttshort',
508                                                                            'By' => 'tt',
509                                                                            'Grid' => 'tt',
510                                                                            'TxTime' => 'tt',
511                                                                           ],
512                                                         policy => [qw(never automatic)],
513                                                         hint => 1,
514                                                         frame => "DX Spots",
515                                                         maxsize => 500,
516                                                         size => [$scr_width * 0.45, $scr_height * 0.45],
517                                                    );
518         $rhvpane->pack1($dx->widget, 1, 0);
519
520         # The wwv list
521         my $rhvbox = Gtk2::VBox->new(0, 1);
522         $wwv = Screen::List->new( fields =>[
523                                                                                 RxTime => 'tt',
524                                                                                 From => 'tt',
525                                                                                 SFI => 'int',
526                                                                                 A => 'int',
527                                                                                 K => 'int',
528                                                                                 Remarks => 'ttshort',
529                                                                                 Hour => 'tt'
530                                                                            ],
531                                                           hint => 1,
532                                                           policy => ['never', 'automatic'],
533                                                           frame => 'WWV Data',
534                                                         );
535         $rhvbox->pack_start($wwv->widget, 1, 1, 0);
536
537         # The wcy list
538         $wcy = Screen::List->new(fields => [
539                                                                                 RxTime => 'tt',
540                                                                                 From => 'tt',
541                                                                                 K => 'int',
542                                                                                 ExpK => 'int',
543                                                                                 A => 'int',
544                                                                                 R => 'int',
545                                                                                 SFI => 'int', 
546                                                                                 SA => 'tt',
547                                                                                 GMF => 'tt',
548                                                                                 Aurora => 'tt',
549                                                                                 Hour => 'tt' 
550                                                                            ],
551                                                          hint => 1,
552                                                          policy => ['never', 'automatic'],
553                                                          frame => 'WCY Data',
554                                                         );
555
556         $rhvbox->pack_start($wcy->widget, 1, 1, 0);
557         $rhvbox->set_size_request($scr_width * 0.45, $scr_height * 0.33);
558         $rhvpane->pack2($rhvbox, 1, 0);
559 }