854f3835eb6c03fd2ba7130aaab0fea498d54b67
[spider.git] / perl / console.pl
1 #!/usr/bin/env perl
2 #
3 # this is the operators console.
4 #
5 # Calling syntax is:-
6 #
7 # console.pl [callsign] 
8 #
9 # if the callsign isn't given then the sysop callsign in DXVars.pm is assumed
10 #
11 # Copyright (c) 1999 Dirk Koopman G1TLH
12 #
13 #
14
15
16 require 5.004;
17 use warnings;
18
19 # search local then perl directories
20 BEGIN {
21         # root of directory tree for this system
22         $root = "/spider"; 
23         $root = $ENV{'DXSPIDER_ROOT'} if $ENV{'DXSPIDER_ROOT'};
24         
25         unshift @INC, "$root/perl";     # this IS the right way round!
26         unshift @INC, "$root/local";
27         $is_win = ($^O =~ /^MS/ || $^O =~ /^OS-2/) ? 1 : 0; # is it Windows?
28 }
29
30 use Mojo::IOLoop;
31
32 use DXVars;
33 use SysVar;
34
35 use Msg;
36 use IntMsg;
37 use DXDebug;
38 use DXUtil;
39 use DXDebug;
40 use IO::File;
41 use Time::HiRes qw(gettimeofday tv_interval);
42 use Curses 1.06;
43 use Text::Wrap;
44
45 use Console;
46
47 #
48 # initialisation
49 #
50
51 $call = "";                     # the callsign being used
52 $conn = 0;                      # the connection object for the cluster
53 $lasttime = time;               # lasttime something happened on the interface
54
55 $connsort = "local";
56 @khistory = ();
57 @shistory = ();
58 $khistpos = 0;
59 $spos = $pos = $lth = 0;
60 $inbuf = "";
61 @time = ();
62 $lastmin = 0;
63 $idle = 0;
64
65
66 #$SIG{WINCH} = sub {@time = gettimeofday};
67
68 sub mydbg
69 {
70         local *STDOUT = undef;
71         dbg(@_);
72 }
73
74 # do the screen initialisation
75 sub do_initscr
76 {
77         $scr = new Curses;
78         if ($has_colors) {
79                 start_color();
80                 init_pair("0", $foreground, $background);
81 #               init_pair(0, $background, $foreground);
82                 init_pair(1, COLOR_RED, $background);
83                 init_pair(2, COLOR_YELLOW, $background);
84                 init_pair(3, COLOR_GREEN, $background);
85                 init_pair(4, COLOR_CYAN, $background);
86                 init_pair(5, COLOR_BLUE, $background);
87                 init_pair(6, COLOR_MAGENTA, $background);
88                 init_pair(7, COLOR_RED, COLOR_BLUE);
89                 init_pair(8, COLOR_YELLOW, COLOR_BLUE);
90                 init_pair(9, COLOR_GREEN, COLOR_BLUE);
91                 init_pair(10, COLOR_CYAN, COLOR_BLUE);
92                 init_pair(11, COLOR_BLUE, COLOR_RED);
93                 init_pair(12, COLOR_MAGENTA, COLOR_BLUE);
94                 init_pair(13, COLOR_YELLOW, COLOR_GREEN);
95                 init_pair(14, COLOR_RED, COLOR_GREEN);
96                 eval { assume_default_colors($foreground, $background) } unless $is_win;
97         }
98
99         $top = $scr->subwin($lines-4, $cols, 0, 0);
100         $top->intrflush(0);
101         $top->scrollok(1);
102         $top->idlok(1);
103         $top->meta(1);
104 #       $scr->addstr($lines-4, 0, '-' x $cols);
105         $bot = $scr->subwin(3, $cols, $lines-3, 0);
106         $bot->intrflush(0);
107         $bot->scrollok(1);
108         $top->idlok(1);
109         $bot->keypad(1);
110         $bot->move(1,0);
111         $bot->meta(1);
112         $bot->nodelay(1);
113         $scr->refresh();
114         
115         $pagel = $lines-4;
116         $mycallcolor = COLOR_PAIR(1) unless $mycallcolor;
117 }
118
119 sub do_resize
120 {
121         endwin() if $scr;
122         initscr();
123         raw();
124         noecho();
125         nonl();
126         $lines = LINES;
127         $cols = COLS;
128         $has_colors = has_colors();
129         do_initscr();
130
131         show_screen();
132 }
133
134 # cease communications
135 sub cease
136 {
137         my $sendz = shift;
138         $conn->disconnect if $conn;
139         endwin();
140         dbgclose();
141         print @_ if @_;
142         exit(0);        
143 }
144
145 # terminate program from signal
146 sub sig_term
147 {
148         cease(1, @_);
149 }
150
151 # determine the colour of the line
152 sub setattr
153 {
154         if ($has_colors) {
155                 foreach my $ref (@colors) {
156                         if ($_[0] =~ m{$$ref[0]}) {
157                                 $top->attrset($$ref[1]);
158                                 last;
159                         }
160                 }
161         }
162 }
163
164 # measure the no of screen lines a line will take
165 sub measure
166 {
167         my $line = shift;
168         return 0 unless $line;
169
170         my $l = length $line;
171         my $lines = int ($l / $cols);
172         $lines++ if $l / $cols > $lines;
173         return $lines;
174 }
175
176 # display the top screen
177 sub show_screen
178 {
179         if ($spos == @shistory - 1) {
180
181                 # if we really are scrolling thru at the end of the history
182                 my $line = $shistory[$spos];
183                 $top->addstr("\n") if $spos > 0;
184                 setattr($line);
185                 $top->addstr($line);
186 #               $top->addstr("\n");
187                 $top->attrset(COLOR_PAIR(0)) if $has_colors;
188                 $spos = @shistory;
189                 
190         } else {
191                 
192                 # anywhere else
193                 my ($i, $l);
194                 my $p = $spos-1;
195                 for ($i = 0; $i < $pagel && $p >= 0; ) {
196                         $l = measure($shistory[$p]);
197                         $i += $l;
198                         $p-- if $i < $pagel;
199                 }
200                 $p = 0 if $p < 0;
201                 
202                 $top->move(0, 0);
203                 $top->attrset(COLOR_PAIR(0)) if $has_colors;
204                 $top->clrtobot();
205                 for ($i = 0; $i < $pagel && $p < @shistory; $p++) {
206                         my $line = $shistory[$p];
207                         my $lines = measure($line);
208                         last if $i + $lines > $pagel;
209                         $top->addstr("\n") if $i;
210                         setattr($line);
211                         $top->addstr($line);
212                         $top->attrset(COLOR_PAIR(0)) if $has_colors;
213                         $i += $lines;
214                 }
215                 $spos = $p;
216                 $spos = @shistory if $spos > @shistory;
217         }
218     my $shl = @shistory;
219         my $size = $lines . 'x' . $cols . '-'; 
220         my $add = "-$spos-$shl";
221     my $time = ztime(time);
222         my $str =  "-" . $time . '-' x ($cols - (length($size) + length($call) + length($add) + length($time) + 1));
223         $scr->addstr($lines-4, 0, $str);
224         
225         $scr->addstr($size);
226         $scr->attrset($mycallcolor) if $has_colors;
227         $scr->addstr($call);
228         $scr->attrset(COLOR_PAIR(0)) if $has_colors;
229     $scr->addstr($add);
230         $scr->refresh();
231 #       $top->refresh();
232 }
233
234 # add a line to the end of the top screen
235 sub addtotop
236 {
237         while (@_) {
238                 my $inbuf = shift;
239                 if ($inbuf =~ s/\x07+$//) {
240                         beep();
241                 }
242                 if (length $inbuf >= $cols) {
243                         $Text::Wrap::Columns = $cols;
244                         push @shistory, wrap('',"\t", $inbuf);
245                 } else {
246                         push @shistory, $inbuf;
247                 }
248                 shift @shistory while @shistory > $maxshist;
249         }
250         show_screen();
251 }
252
253 # handle incoming messages
254 sub rec_socket
255 {
256         my ($con, $msg, $err) = @_;
257         if (defined $err && $err) {
258                 cease(1);
259         }
260         if (defined $msg) {
261                 my ($sort, $call, $line) = $msg =~ /^(\w)([^\|]+)\|(.*)$/;
262                 
263                 $line =~ s/[\x00-\x06\x08\x0a-\x19\x1b-\x1f\x80-\x9f]/./g;         # immutable CSI sequence + control characters
264                 if ($sort && $sort eq 'D') {
265                         $line = " " unless length($line);
266                         addtotop($line);
267                 } elsif ($sort && $sort eq 'Z') { # end, disconnect, go, away .....
268                         cease(0);
269                 }         
270                 # ******************************************************
271                 # ******************************************************
272                 # any other sorts that might happen are silently ignored.
273                 # ******************************************************
274                 # ******************************************************
275         } else {
276                 cease(0);
277         }
278         $top->refresh();
279         $lasttime = time; 
280 }
281
282 sub rec_stdin
283 {
284         my $r = shift;;
285         
286         #  my $prbuf;
287         #  $prbuf = $buf;
288         #  $prbuf =~ s/\r/\\r/;
289         #  $prbuf =~ s/\n/\\n/;
290         #  print "sys: $r ($prbuf)\n";
291         if (defined $r) {
292
293                 $r = '0' if !$r;
294                 
295                 if ($r eq KEY_ENTER || $r eq "\n" || $r eq "\r") {
296                         
297                         # save the lines
298                         $inbuf = " " unless length $inbuf;
299
300                         # check for a pling and do a search back for a command
301                         if ($inbuf =~ /^!/o) {
302                                 my $i;
303                                 $inbuf =~ s/^!//o;
304                                 for ($i = $#khistory; $i >= 0; $i--) {
305                                         if ($khistory[$i] =~ /^$inbuf/) {
306                                                 $inbuf = $khistory[$i];
307                                                 last;
308                                         }
309                                 }
310                                 if ($i < 0) {
311                                         beep();
312                                         return;
313                                 }
314                         }
315                         push @khistory, $inbuf if length $inbuf;
316                         shift @khistory if @khistory > $maxkhist;
317                         $khistpos = @khistory;
318                         $bot->move(0,0);
319                         $bot->clrtoeol();
320                         $bot->addstr(substr($inbuf, 0, $cols));
321
322                         # add it to the monitor window
323                         unless ($spos == @shistory) {
324                                 $spos = @shistory;
325                                 show_screen();
326                         };
327                         addtotop($inbuf);
328                 
329                         # send it to the cluster
330                         $conn->send_later("I$call|$inbuf");
331                         $inbuf = "";
332                         $pos = $lth = 0;
333                 } elsif ($r eq KEY_UP || $r eq "\020") {
334                         if ($khistpos > 0) {
335                                 --$khistpos;
336                                 $inbuf = $khistory[$khistpos];
337                                 $pos = $lth = length $inbuf;
338                         } else {
339                                 beep();
340                         }
341                 } elsif ($r eq KEY_DOWN || $r eq "\016") {
342                         if ($khistpos < @khistory - 1) {
343                                 ++$khistpos;
344                                 $inbuf = $khistory[$khistpos];
345                                 $pos = $lth = length $inbuf;
346                         } else {
347                                 beep();
348                         }
349                 } elsif ($r eq KEY_PPAGE || $r eq "\032") {
350                         if ($spos > 0) {
351                                 my ($i, $l);
352                                 for ($i = 0; $i < $pagel-1 && $spos >= 0; ) {
353                                         $l = measure($shistory[$spos]);
354                                         $i += $l;
355                                         $spos-- if $i <= $pagel;
356                                 }
357                                 $spos = 0 if $spos < 0;
358                                 show_screen();
359                         } else {
360                                 beep();
361                         }
362                 } elsif ($r eq KEY_NPAGE || $r eq "\026") {
363                         if ($spos < @shistory - 1) {
364                                 my ($i, $l);
365                                 for ($i = 0; $i <= $pagel && $spos <= @shistory; ) {
366                                         $l = measure($shistory[$spos]);
367                                         $i += $l;
368                                         $spos++ if $i <= $pagel;
369                                 }
370                                 $spos = @shistory if $spos >= @shistory - 1;
371                                 show_screen();
372                         } else {
373                                 beep();
374                         }
375                 } elsif ($r eq KEY_LEFT || $r eq "\002") {
376                         if ($pos > 0) {
377                                 --$pos;
378                         } else {
379                                 beep();
380                         }
381                 } elsif ($r eq KEY_RIGHT || $r eq "\006") {
382                         if ($pos < $lth) {
383                                 ++$pos;
384                         } else {
385                                 beep();
386                         }
387                 } elsif ($r eq KEY_HOME || $r eq "\001") {
388                         $pos = 0;
389                 } elsif ($r eq KEY_END || $r eq "\005") {
390                         $pos = $lth;
391                 } elsif ($r eq KEY_BACKSPACE || $r eq "\010" || $r eq "\x7f") {
392                         if ($pos > 0) {
393                                 my $a = substr($inbuf, 0, $pos-1);
394                                 my $b = substr($inbuf, $pos) if $pos < $lth;
395                                 $b = "" unless $b;
396                                 
397                                 $inbuf = $a . $b;
398                                 --$lth;
399                                 --$pos;
400                         } else {
401                                 beep();
402                         }
403                 } elsif ($r eq KEY_DC || $r eq "\004") {
404                         if ($pos < $lth) {
405                                 my $a = substr($inbuf, 0, $pos);
406                                 my $b = substr($inbuf, $pos+1) if $pos < $lth;
407                                 $b = "" unless $b;
408                                 
409                                 $inbuf = $a . $b;
410                                 --$lth;
411                         } else {
412                                 beep();
413                         }
414                 } elsif ($r eq KEY_RESIZE || $r eq "\0632") {
415                         do_resize();
416                         return;
417                 } elsif (defined $r && is_pctext($r)) {
418                         # move the top screen back to the bottom if you type something
419                         if ($spos < @shistory) {
420                                 $spos = @shistory;
421                                 show_screen();
422                         }
423
424                 #       $r = ($r lt ' ' || $r gt "\x7e") ? sprintf("'%x", ord $r) : $r;
425                         
426                         # insert the character into the keyboard buffer
427                         if ($pos < $lth) {
428                                 my $a = substr($inbuf, 0, $pos);
429                                 my $b = substr($inbuf, $pos);
430                                 $inbuf = $a . $r . $b;
431                         } else {
432                                 $inbuf .= $r;
433                         }
434                         $pos++;
435                         $lth++;
436                 } elsif ($r eq "\014" || $r eq "\022") {
437                         touchwin(curscr, 1);
438                         refresh(curscr);
439                         return;
440                 } elsif ($r eq "\013") {
441                         $inbuf = substr($inbuf, 0, $pos);
442                         $lth = length $inbuf;
443                 } else {
444                         beep();
445                 }
446                 $bot->move(1, 0);
447                 $bot->clrtobot();
448                 $bot->addstr($inbuf);
449         } 
450         $bot->move(1, $pos);
451         $bot->refresh();
452 }
453
454 sub idle_loop
455 {
456         my $t;
457         
458         $t = time;
459         if ($t > $lasttime) {
460                 my ($min)= (gmtime($t))[1];
461                 if ($min != $lastmin) {
462                         show_screen();
463                         $lastmin = $min;
464                 }
465                 $lasttime = $t;
466         }
467         my $ch = $bot->getch();
468         if (@time && tv_interval(\@time, [gettimeofday]) >= 1) {
469                 next;
470         }
471         if (defined $ch) {
472                 if ($ch ne '-1') {
473                         rec_stdin($ch);
474                 }
475         }
476         $top->refresh() if $top->is_wintouched;
477         $bot->refresh();
478 }
479
480 sub on_connect
481 {
482         my $conn = shift;
483         $conn->send_later("A$call|$connsort width=$cols");
484         $conn->send_later("I$call|set/page $maxshist");
485         #$conn->send_later("I$call|set/nobeep");
486 }
487
488 sub on_disconnect
489 {
490         $conn = shift;
491         Mojo::IOLoop->remove($idle);
492         Mojo::IOLoop->stop;
493 }
494
495 #
496 # deal with args
497 #
498
499 $call = uc shift @ARGV if @ARGV;
500 $call = uc $myalias if !$call;
501 my ($scall, $ssid) = split /-/, $call;
502 $ssid = undef unless $ssid && $ssid =~ /^\d+$/;  
503 if ($ssid) {
504         $ssid = 15 if $ssid > 15;
505         $call = "$scall-$ssid";
506 }
507
508 if ($call eq $mycall) {
509         print "You cannot connect as your cluster callsign ($mycall)\n";
510         exit(0);
511 }
512
513 dbginit();
514
515 unless ($DB::VERSION) {
516         $SIG{'INT'} = \&sig_term;
517         $SIG{'TERM'} = \&sig_term;
518 }
519
520 $SIG{'HUP'} = \&sig_term;
521
522 # start up
523 do_resize();
524
525 $SIG{__DIE__} = \&sig_term;
526
527 $Text::Wrap::Columns = $cols;
528
529 my $lastmin = 0;
530
531
532 $conn = IntMsg->connect($clusteraddr, $clusterport, rproc => \&rec_socket);
533 $conn->{on_connect} = \&on_connect;
534 $conn->{on_disconnect} = \&on_disconnect;
535
536 $idle = Mojo::IOLoop->recurring(0.100 => \&idle_loop);
537 Mojo::IOLoop->start;
538
539
540 cease(0);