Screen is a web portal to the terminal! More importantly, it is a collaboration tool that allows users to share a terminal session online.
The Screen website is no longer active. There are issues with piping information back and forth to a terminal, especially in interactive CLI’s. There are also security issues, so I have decommissioned it. But this was still a great learning experience.
You can view the full code on GitHub. The below preview is the room.pm module. It creates room objects for users to collaborate in. It also creates a shell for each room and executes the terminal commands.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
package room; use strict; use warnings; use feature 'switch'; use helpers; use set; use shell; ######################################### GLOBALS ######################################## our %nametoroom = (); #################################### HELPER FUNCTIONS #################################### our @namepool = (); open( my $FH, '<', 'public/lexis.txt' ) or die 'Could not open the lexis!!!'; push @namepool => trim(lc $_) while( <$FH> ); # rooms are stored as lowercase, so the keys must be lc too! close( $FH ) or die 'Could not CLOSE the lexis!'; sub rand_name{ my $randindex = int( $#namepool * rand() ); return $namepool[$randindex] } sub name_to_room{ # creates a new room if necessary my ($name) = @_; return $nametoroom{$name} //= room->new(name=>$name) or die 'Bad room name string!' # adds the room to our hash if necessary } sub htmlify{ # this takes any amount of terminal stdout and converts it to html. my ( $str, $prompt ) = @_; # put a span around the commands... # $prompt = quotemeta $prompt; # $str =~ s|^(.*)\n|$1\n|; # the very first command is NOT preceded by a prompt. # $str =~ s|\n?$prompt(.*)\n|\n$1\n|g; # this also makes sure that prompts always start on new lines. # $str =~ s|\n?$prompt$|\n|; # delete final prompt. Also, all endings have newlines regardless of what bash outputted. # $str =~ s|\n$|\n\n|g; # for an outputless command, let's display an empty line to show that. $str =~ s|\n| |g; return $str } ######################################## NEW ROOM ######################################## sub new{ my $packagename = shift; my %options = @_; # includes the NAME of the room. my $room = bless { name => $options{name}, shell => undef, controllers => set->new, output => '', } => $packagename; $room->shell('sh'); return $room } ########################################## ROOM ########################################## sub name{ my $room = shift; return $room{name} } sub shell{ my $room = shift; my ($shellname) = @_; if( defined $shellname ){ $room->shell->close if defined $room->shell; $room{shell} = shell->new( $shellname, $room ); my $divider = "\n###############################################################\n"; $room->append($divider."Welcome to a new $shellname shell!\n\n"); } return $room{shell} } sub controllers{ # this is a SET of controllers my $room = shift; return $room{controllers} } sub add_controller{ my $room = shift; my ($controller) = @_; $room->controllers->add_element($controller) } sub remove_controller{ say 'removing a controller...'; my $room = shift; my ($controller) = @_; $room->controllers->remove_element($controller); # check if the room is empty. if so, close the room. if( $room->controllers->is_empty ){ say 'closing the room...'; $room->close } } sub output{ # gets all the output of a room for a new self joining in my $room = shift; return $room{output} } sub append_to_output{ my $room = shift; my ($string) = @_; $room{output} .= $string; } sub append{ my $room = shift; my ($string) = @_; $room->append_to_output($string); # my $html = htmlify( $string, $room->shell->prompt ); my $html = htmlify( $string ); $room->jblast( command=>'append', output=>$html ); #say "output is now $room{output}"; } sub jblast{ # blast is replaced with jblast, which includes the json in it my $room = shift; #say 'jblasting to '.scalar $room->controllers->array.' controllers.'; my %ball = @_; say 'ball is '."@_"; foreach( $room->controllers->array ){ #say 'i have a controller '."$_"; $_->jsend(%ball) } #say 'done WITH CONTROLLERS'; } use overload '""' => \&stringify; sub stringify{ my $room = shift; my $toprint; { local $" = ','; $toprint .= 'Roomname is '.$room->name."\n"; my @controllers = $room->controllers->array; $toprint .= "Controllers are: @controllers\n"; } return $toprint } sub command{ # executes a shell command and relays the output to all controllers. my $room = shift; my ($command) = @_; # incoming command does not have a \n at the end. say "command is $command"; #say "Bin is $Bin"; if( $command =~ /^(shell) +([^ ]*)/i ){ # capture special room commands: shells my $keyword = $1; my $argument = lc trim $2; given($keyword){ when('shell'){ # the argument has already been verified in $self->command $room->shell($argument); $room->jblast( command=>'set', id=>'shellname', html=>$argument, color=>"#f12772" ); } } } else{ # my $outappend = $room->shell->command($command); my $outappend = "$command " . $room->shell->command($command); $room->append($outappend); } } sub close{ my $room = shift; $room->shell->close; # tell each controller in the room to leave the room (reset roomname attribute, remove self from room->controllers, update GUI foreach my $self ( $room->controllers->array ){ $self->leave_room } delete $nametoroom{ $room->name }; # delete the room from the nametoroom hash } 1 |